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."
This commit is contained in:
parent
0688dd0c2c
commit
19dddd7adf
32 changed files with 400 additions and 84 deletions
|
@ -10,7 +10,6 @@ message IncomingPushMessageSignal {
|
||||||
KEY_EXCHANGE = 2;
|
KEY_EXCHANGE = 2;
|
||||||
PREKEY_BUNDLE = 3;
|
PREKEY_BUNDLE = 3;
|
||||||
PLAINTEXT = 4;
|
PLAINTEXT = 4;
|
||||||
ADVISORY = 5;
|
|
||||||
}
|
}
|
||||||
optional Type type = 1;
|
optional Type type = 1;
|
||||||
optional string source = 2;
|
optional string source = 2;
|
||||||
|
@ -23,9 +22,9 @@ message IncomingPushMessageSignal {
|
||||||
|
|
||||||
message PushMessageContent {
|
message PushMessageContent {
|
||||||
message AttachmentPointer {
|
message AttachmentPointer {
|
||||||
optional fixed64 id = 1;
|
optional fixed64 id = 1;
|
||||||
optional string contentType = 2;
|
optional string contentType = 2;
|
||||||
optional bytes key = 3;
|
optional bytes key = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GroupContext {
|
message GroupContext {
|
||||||
|
@ -37,14 +36,19 @@ message PushMessageContent {
|
||||||
ADD = 4;
|
ADD = 4;
|
||||||
QUIT = 5;
|
QUIT = 5;
|
||||||
}
|
}
|
||||||
optional bytes id = 1;
|
optional bytes id = 1;
|
||||||
optional Type type = 2;
|
optional Type type = 2;
|
||||||
optional string name = 3;
|
optional string name = 3;
|
||||||
repeated string members = 4;
|
repeated string members = 4;
|
||||||
optional AttachmentPointer avatar = 5;
|
optional AttachmentPointer avatar = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional string body = 1;
|
enum Flags {
|
||||||
|
END_SESSION = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
optional string body = 1;
|
||||||
repeated AttachmentPointer attachments = 2;
|
repeated AttachmentPointer attachments = 2;
|
||||||
optional GroupContext group = 3;
|
optional GroupContext group = 3;
|
||||||
|
optional uint32 flags = 4;
|
||||||
}
|
}
|
|
@ -23,4 +23,4 @@ message KeyExchangeMessage {
|
||||||
optional bytes baseKey = 2;
|
optional bytes baseKey = 2;
|
||||||
optional bytes ephemeralKey = 3;
|
optional bytes ephemeralKey = 3;
|
||||||
optional bytes identityKey = 4;
|
optional bytes identityKey = 4;
|
||||||
}
|
}
|
|
@ -31,6 +31,7 @@ import javax.crypto.spec.SecretKeySpec;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.text.ParseException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -118,6 +119,8 @@ public class AttachmentCipher {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
} catch (BadPaddingException e) {
|
} catch (BadPaddingException e) {
|
||||||
throw new InvalidMessageException(e);
|
throw new InvalidMessageException(e);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
throw new InvalidMessageException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ import org.whispersystems.textsecure.util.Hex;
|
||||||
import org.whispersystems.textsecure.util.Util;
|
import org.whispersystems.textsecure.util.Util;
|
||||||
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.text.ParseException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import javax.crypto.Mac;
|
import javax.crypto.Mac;
|
||||||
|
@ -38,7 +39,7 @@ public class WhisperMessageV2 implements CiphertextMessage {
|
||||||
byte[] mac = messageParts[2];
|
byte[] mac = messageParts[2];
|
||||||
|
|
||||||
if (Conversions.highBitsToInt(version) != CURRENT_VERSION) {
|
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);
|
WhisperMessage whisperMessage = WhisperMessage.parseFrom(message);
|
||||||
|
@ -59,6 +60,8 @@ public class WhisperMessageV2 implements CiphertextMessage {
|
||||||
throw new InvalidMessageException(e);
|
throw new InvalidMessageException(e);
|
||||||
} catch (InvalidKeyException e) {
|
} catch (InvalidKeyException e) {
|
||||||
throw new InvalidMessageException(e);
|
throw new InvalidMessageException(e);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
throw new InvalidMessageException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,6 @@ public final class PushMessageProtos {
|
||||||
KEY_EXCHANGE(2, 2),
|
KEY_EXCHANGE(2, 2),
|
||||||
PREKEY_BUNDLE(3, 3),
|
PREKEY_BUNDLE(3, 3),
|
||||||
PLAINTEXT(4, 4),
|
PLAINTEXT(4, 4),
|
||||||
ADVISORY(5, 5),
|
|
||||||
;
|
;
|
||||||
|
|
||||||
public static final int UNKNOWN_VALUE = 0;
|
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 KEY_EXCHANGE_VALUE = 2;
|
||||||
public static final int PREKEY_BUNDLE_VALUE = 3;
|
public static final int PREKEY_BUNDLE_VALUE = 3;
|
||||||
public static final int PLAINTEXT_VALUE = 4;
|
public static final int PLAINTEXT_VALUE = 4;
|
||||||
public static final int ADVISORY_VALUE = 5;
|
|
||||||
|
|
||||||
|
|
||||||
public final int getNumber() { return value; }
|
public final int getNumber() { return value; }
|
||||||
|
@ -90,7 +88,6 @@ public final class PushMessageProtos {
|
||||||
case 2: return KEY_EXCHANGE;
|
case 2: return KEY_EXCHANGE;
|
||||||
case 3: return PREKEY_BUNDLE;
|
case 3: return PREKEY_BUNDLE;
|
||||||
case 4: return PLAINTEXT;
|
case 4: return PLAINTEXT;
|
||||||
case 5: return ADVISORY;
|
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,7 +118,7 @@ public final class PushMessageProtos {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Type[] VALUES = {
|
private static final Type[] VALUES = {
|
||||||
UNKNOWN, CIPHERTEXT, KEY_EXCHANGE, PREKEY_BUNDLE, PLAINTEXT, ADVISORY,
|
UNKNOWN, CIPHERTEXT, KEY_EXCHANGE, PREKEY_BUNDLE, PLAINTEXT,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static Type valueOf(
|
public static Type valueOf(
|
||||||
|
@ -819,6 +816,10 @@ public final class PushMessageProtos {
|
||||||
boolean hasGroup();
|
boolean hasGroup();
|
||||||
org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext getGroup();
|
org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext getGroup();
|
||||||
org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContextOrBuilder getGroupOrBuilder();
|
org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContextOrBuilder getGroupOrBuilder();
|
||||||
|
|
||||||
|
// optional uint32 flags = 4;
|
||||||
|
boolean hasFlags();
|
||||||
|
int getFlags();
|
||||||
}
|
}
|
||||||
public static final class PushMessageContent extends
|
public static final class PushMessageContent extends
|
||||||
com.google.protobuf.GeneratedMessage
|
com.google.protobuf.GeneratedMessage
|
||||||
|
@ -848,6 +849,72 @@ public final class PushMessageProtos {
|
||||||
return org.whispersystems.textsecure.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_fieldAccessorTable;
|
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<Flags>
|
||||||
|
internalGetValueMap() {
|
||||||
|
return internalValueMap;
|
||||||
|
}
|
||||||
|
private static com.google.protobuf.Internal.EnumLiteMap<Flags>
|
||||||
|
internalValueMap =
|
||||||
|
new com.google.protobuf.Internal.EnumLiteMap<Flags>() {
|
||||||
|
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
|
public interface AttachmentPointerOrBuilder
|
||||||
extends com.google.protobuf.MessageOrBuilder {
|
extends com.google.protobuf.MessageOrBuilder {
|
||||||
|
|
||||||
|
@ -2243,10 +2310,21 @@ public final class PushMessageProtos {
|
||||||
return group_;
|
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() {
|
private void initFields() {
|
||||||
body_ = "";
|
body_ = "";
|
||||||
attachments_ = java.util.Collections.emptyList();
|
attachments_ = java.util.Collections.emptyList();
|
||||||
group_ = org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.getDefaultInstance();
|
group_ = org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.GroupContext.getDefaultInstance();
|
||||||
|
flags_ = 0;
|
||||||
}
|
}
|
||||||
private byte memoizedIsInitialized = -1;
|
private byte memoizedIsInitialized = -1;
|
||||||
public final boolean isInitialized() {
|
public final boolean isInitialized() {
|
||||||
|
@ -2269,6 +2347,9 @@ public final class PushMessageProtos {
|
||||||
if (((bitField0_ & 0x00000002) == 0x00000002)) {
|
if (((bitField0_ & 0x00000002) == 0x00000002)) {
|
||||||
output.writeMessage(3, group_);
|
output.writeMessage(3, group_);
|
||||||
}
|
}
|
||||||
|
if (((bitField0_ & 0x00000004) == 0x00000004)) {
|
||||||
|
output.writeUInt32(4, flags_);
|
||||||
|
}
|
||||||
getUnknownFields().writeTo(output);
|
getUnknownFields().writeTo(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2290,6 +2371,10 @@ public final class PushMessageProtos {
|
||||||
size += com.google.protobuf.CodedOutputStream
|
size += com.google.protobuf.CodedOutputStream
|
||||||
.computeMessageSize(3, group_);
|
.computeMessageSize(3, group_);
|
||||||
}
|
}
|
||||||
|
if (((bitField0_ & 0x00000004) == 0x00000004)) {
|
||||||
|
size += com.google.protobuf.CodedOutputStream
|
||||||
|
.computeUInt32Size(4, flags_);
|
||||||
|
}
|
||||||
size += getUnknownFields().getSerializedSize();
|
size += getUnknownFields().getSerializedSize();
|
||||||
memoizedSerializedSize = size;
|
memoizedSerializedSize = size;
|
||||||
return size;
|
return size;
|
||||||
|
@ -2430,6 +2515,8 @@ public final class PushMessageProtos {
|
||||||
groupBuilder_.clear();
|
groupBuilder_.clear();
|
||||||
}
|
}
|
||||||
bitField0_ = (bitField0_ & ~0x00000004);
|
bitField0_ = (bitField0_ & ~0x00000004);
|
||||||
|
flags_ = 0;
|
||||||
|
bitField0_ = (bitField0_ & ~0x00000008);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2489,6 +2576,10 @@ public final class PushMessageProtos {
|
||||||
} else {
|
} else {
|
||||||
result.group_ = groupBuilder_.build();
|
result.group_ = groupBuilder_.build();
|
||||||
}
|
}
|
||||||
|
if (((from_bitField0_ & 0x00000008) == 0x00000008)) {
|
||||||
|
to_bitField0_ |= 0x00000004;
|
||||||
|
}
|
||||||
|
result.flags_ = flags_;
|
||||||
result.bitField0_ = to_bitField0_;
|
result.bitField0_ = to_bitField0_;
|
||||||
onBuilt();
|
onBuilt();
|
||||||
return result;
|
return result;
|
||||||
|
@ -2537,6 +2628,9 @@ public final class PushMessageProtos {
|
||||||
if (other.hasGroup()) {
|
if (other.hasGroup()) {
|
||||||
mergeGroup(other.getGroup());
|
mergeGroup(other.getGroup());
|
||||||
}
|
}
|
||||||
|
if (other.hasFlags()) {
|
||||||
|
setFlags(other.getFlags());
|
||||||
|
}
|
||||||
this.mergeUnknownFields(other.getUnknownFields());
|
this.mergeUnknownFields(other.getUnknownFields());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -2588,6 +2682,11 @@ public final class PushMessageProtos {
|
||||||
setGroup(subBuilder.buildPartial());
|
setGroup(subBuilder.buildPartial());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 32: {
|
||||||
|
bitField0_ |= 0x00000008;
|
||||||
|
flags_ = input.readUInt32();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2906,6 +3005,27 @@ public final class PushMessageProtos {
|
||||||
return groupBuilder_;
|
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)
|
// @@protoc_insertion_point(builder_scope:textsecure.PushMessageContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2947,28 +3067,28 @@ public final class PushMessageProtos {
|
||||||
static {
|
static {
|
||||||
java.lang.String[] descriptorData = {
|
java.lang.String[] descriptorData = {
|
||||||
"\n\037IncomingPushMessageSignal.proto\022\ntexts" +
|
"\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" +
|
"ype\030\001 \001(\0162*.textsecure.IncomingPushMessa" +
|
||||||
"geSignal.Type\022\016\n\006source\030\002 \001(\t\022\024\n\014sourceD" +
|
"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" +
|
"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" +
|
"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" +
|
"\rPREKEY_BUNDLE\020\003\022\r\n\tPLAINTEXT\020\004\"\234\004\n\022Push" +
|
||||||
"ORY\020\005\"\363\003\n\022PushMessageContent\022\014\n\004body\030\001 \001" +
|
"MessageContent\022\014\n\004body\030\001 \001(\t\022E\n\013attachme" +
|
||||||
"(\t\022E\n\013attachments\030\002 \003(\01320.textsecure.Pus",
|
"nts\030\002 \003(\01320.textsecure.PushMessageConten",
|
||||||
"hMessageContent.AttachmentPointer\022:\n\005gro" +
|
"t.AttachmentPointer\022:\n\005group\030\003 \001(\0132+.tex" +
|
||||||
"up\030\003 \001(\0132+.textsecure.PushMessageContent" +
|
"tsecure.PushMessageContent.GroupContext\022" +
|
||||||
".GroupContext\032A\n\021AttachmentPointer\022\n\n\002id" +
|
"\r\n\005flags\030\004 \001(\r\032A\n\021AttachmentPointer\022\n\n\002i" +
|
||||||
"\030\001 \001(\006\022\023\n\013contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(\014" +
|
"d\030\001 \001(\006\022\023\n\013contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(" +
|
||||||
"\032\210\002\n\014GroupContext\022\n\n\002id\030\001 \001(\014\022>\n\004type\030\002 " +
|
"\014\032\210\002\n\014GroupContext\022\n\n\002id\030\001 \001(\014\022>\n\004type\030\002" +
|
||||||
"\001(\01620.textsecure.PushMessageContent.Grou" +
|
" \001(\01620.textsecure.PushMessageContent.Gro" +
|
||||||
"pContext.Type\022\014\n\004name\030\003 \001(\t\022\017\n\007members\030\004" +
|
"upContext.Type\022\014\n\004name\030\003 \001(\t\022\017\n\007members\030" +
|
||||||
" \003(\t\022@\n\006avatar\030\005 \001(\01320.textsecure.PushMe" +
|
"\004 \003(\t\022@\n\006avatar\030\005 \001(\01320.textsecure.PushM" +
|
||||||
"ssageContent.AttachmentPointer\"K\n\004Type\022\013" +
|
"essageContent.AttachmentPointer\"K\n\004Type\022" +
|
||||||
"\n\007UNKNOWN\020\000\022\n\n\006CREATE\020\001\022\n\n\006MODIFY\020\002\022\013\n\007D",
|
"\013\n\007UNKNOWN\020\000\022\n\n\006CREATE\020\001\022\n\n\006MODIFY\020\002\022\013\n\007",
|
||||||
"ELIVER\020\003\022\007\n\003ADD\020\004\022\010\n\004QUIT\020\005B7\n\"org.whisp" +
|
"DELIVER\020\003\022\007\n\003ADD\020\004\022\010\n\004QUIT\020\005\"\030\n\005Flags\022\017\n" +
|
||||||
"ersystems.textsecure.pushB\021PushMessagePr" +
|
"\013END_SESSION\020\001B7\n\"org.whispersystems.tex" +
|
||||||
"otos"
|
"tsecure.pushB\021PushMessageProtos"
|
||||||
};
|
};
|
||||||
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
|
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
|
||||||
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
|
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
|
||||||
|
@ -2988,7 +3108,7 @@ public final class PushMessageProtos {
|
||||||
internal_static_textsecure_PushMessageContent_fieldAccessorTable = new
|
internal_static_textsecure_PushMessageContent_fieldAccessorTable = new
|
||||||
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
|
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
|
||||||
internal_static_textsecure_PushMessageContent_descriptor,
|
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.class,
|
||||||
org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.Builder.class);
|
org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent.Builder.class);
|
||||||
internal_static_textsecure_PushMessageContent_AttachmentPointer_descriptor =
|
internal_static_textsecure_PushMessageContent_AttachmentPointer_descriptor =
|
||||||
|
|
|
@ -13,6 +13,7 @@ import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
import java.text.ParseException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -45,7 +46,15 @@ public class Util {
|
||||||
return parts;
|
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][];
|
byte[][] parts = new byte[3][];
|
||||||
|
|
||||||
parts[0] = new byte[firstLength];
|
parts[0] = new byte[firstLength];
|
||||||
|
|
|
@ -80,6 +80,7 @@ import org.thoughtcrime.securesms.recipients.Recipients;
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||||
import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage;
|
import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage;
|
||||||
|
import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage;
|
||||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||||
import org.thoughtcrime.securesms.util.ActionBarUtil;
|
import org.thoughtcrime.securesms.util.ActionBarUtil;
|
||||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
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.InvalidMessageException;
|
||||||
import org.whispersystems.textsecure.crypto.MasterCipher;
|
import org.whispersystems.textsecure.crypto.MasterCipher;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
|
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||||
import org.whispersystems.textsecure.storage.Session;
|
import org.whispersystems.textsecure.storage.Session;
|
||||||
|
import org.whispersystems.textsecure.storage.SessionRecordV2;
|
||||||
import org.whispersystems.textsecure.util.Util;
|
import org.whispersystems.textsecure.util.Util;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -368,9 +371,23 @@ public class ConversationActivity extends PassphraseRequiredSherlockFragmentActi
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
if (isSingleConversation()) {
|
if (isSingleConversation()) {
|
||||||
Session.abortSessionFor(ConversationActivity.this, getRecipients().getPrimaryRecipient());
|
ConversationActivity self = ConversationActivity.this;
|
||||||
initializeSecurity();
|
Recipient recipient = getRecipients().getPrimaryRecipient();
|
||||||
initializeTitleBar();
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -236,7 +236,7 @@ public class ReceiveKeyActivity extends Activity {
|
||||||
|
|
||||||
DecryptingQueue.scheduleDecryption(ReceiveKeyActivity.this, masterSecret, messageId,
|
DecryptingQueue.scheduleDecryption(ReceiveKeyActivity.this, masterSecret, messageId,
|
||||||
threadId, recipient.getNumber(), recipientDeviceId,
|
threadId, recipient.getNumber(), recipientDeviceId,
|
||||||
messageBody, true, false);
|
messageBody, true, false, false);
|
||||||
} catch (InvalidKeyIdException e) {
|
} catch (InvalidKeyIdException e) {
|
||||||
Log.w("ReceiveKeyActivity", e);
|
Log.w("ReceiveKeyActivity", e);
|
||||||
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
|
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
|
||||||
|
|
|
@ -81,11 +81,12 @@ public class DecryptingQueue {
|
||||||
|
|
||||||
public static void scheduleDecryption(Context context, MasterSecret masterSecret,
|
public static void scheduleDecryption(Context context, MasterSecret masterSecret,
|
||||||
long messageId, long threadId, String originator, int deviceId,
|
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,
|
DecryptionWorkItem runnable = new DecryptionWorkItem(context, masterSecret, messageId, threadId,
|
||||||
originator, deviceId, body,
|
originator, deviceId, body,
|
||||||
isSecureMessage, isKeyExchange);
|
isSecureMessage, isKeyExchange, isEndSession);
|
||||||
executor.execute(runnable);
|
executor.execute(runnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,10 +168,11 @@ public class DecryptingQueue {
|
||||||
int originatorDeviceId = record.getRecipientDeviceId();
|
int originatorDeviceId = record.getRecipientDeviceId();
|
||||||
boolean isSecureMessage = record.isSecure();
|
boolean isSecureMessage = record.isSecure();
|
||||||
boolean isKeyExchange = record.isKeyExchange();
|
boolean isKeyExchange = record.isKeyExchange();
|
||||||
|
boolean isEndSession = record.isEndSession();
|
||||||
|
|
||||||
scheduleDecryption(context, masterSecret, messageId, threadId,
|
scheduleDecryption(context, masterSecret, messageId, threadId,
|
||||||
originator, originatorDeviceId, body,
|
originator, originatorDeviceId, body,
|
||||||
isSecureMessage, isKeyExchange);
|
isSecureMessage, isKeyExchange, isEndSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class PushDecryptionWorkItem implements Runnable {
|
private static class PushDecryptionWorkItem implements Runnable {
|
||||||
|
@ -332,10 +334,11 @@ public class DecryptingQueue {
|
||||||
private final int deviceId;
|
private final int deviceId;
|
||||||
private final boolean isSecureMessage;
|
private final boolean isSecureMessage;
|
||||||
private final boolean isKeyExchange;
|
private final boolean isKeyExchange;
|
||||||
|
private final boolean isEndSession;
|
||||||
|
|
||||||
public DecryptionWorkItem(Context context, MasterSecret masterSecret, long messageId, long threadId,
|
public DecryptionWorkItem(Context context, MasterSecret masterSecret, long messageId, long threadId,
|
||||||
String originator, int deviceId, String body, boolean isSecureMessage,
|
String originator, int deviceId, String body, boolean isSecureMessage,
|
||||||
boolean isKeyExchange)
|
boolean isKeyExchange, boolean isEndSession)
|
||||||
{
|
{
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.messageId = messageId;
|
this.messageId = messageId;
|
||||||
|
@ -346,6 +349,7 @@ public class DecryptingQueue {
|
||||||
this.deviceId = deviceId;
|
this.deviceId = deviceId;
|
||||||
this.isSecureMessage = isSecureMessage;
|
this.isSecureMessage = isSecureMessage;
|
||||||
this.isKeyExchange = isKeyExchange;
|
this.isKeyExchange = isKeyExchange;
|
||||||
|
this.isEndSession = isEndSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleRemoteAsymmetricEncrypt() {
|
private void handleRemoteAsymmetricEncrypt() {
|
||||||
|
@ -368,6 +372,13 @@ public class DecryptingQueue {
|
||||||
byte[] paddedPlaintext = sessionCipher.decrypt(decodedCiphertext);
|
byte[] paddedPlaintext = sessionCipher.decrypt(decodedCiphertext);
|
||||||
|
|
||||||
plaintextBody = new String(transportDetails.getStrippedPaddingMessageBody(paddedPlaintext));
|
plaintextBody = new String(transportDetails.getStrippedPaddingMessageBody(paddedPlaintext));
|
||||||
|
|
||||||
|
if (isEndSession &&
|
||||||
|
"TERMINATE".equals(plaintextBody) &&
|
||||||
|
SessionRecordV2.hasSession(context, masterSecret, recipientDevice))
|
||||||
|
{
|
||||||
|
Session.abortSessionFor(context, recipient);
|
||||||
|
}
|
||||||
} catch (InvalidMessageException e) {
|
} catch (InvalidMessageException e) {
|
||||||
Log.w("DecryptionQueue", e);
|
Log.w("DecryptionQueue", e);
|
||||||
database.markAsDecryptFailed(messageId);
|
database.markAsDecryptFailed(messageId);
|
||||||
|
@ -441,7 +452,7 @@ public class DecryptingQueue {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (isSecureMessage) {
|
if (isSecureMessage || isEndSession) {
|
||||||
handleRemoteAsymmetricEncrypt();
|
handleRemoteAsymmetricEncrypt();
|
||||||
} else {
|
} else {
|
||||||
handleLocalAsymmetricEncrypt();
|
handleLocalAsymmetricEncrypt();
|
||||||
|
|
|
@ -18,10 +18,12 @@
|
||||||
package org.thoughtcrime.securesms.crypto;
|
package org.thoughtcrime.securesms.crypto;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
|
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||||
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||||
|
@ -45,4 +47,12 @@ public abstract class KeyExchangeProcessor {
|
||||||
return new KeyExchangeProcessorV2(context, masterSecret, recipientDevice);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
package org.thoughtcrime.securesms.crypto;
|
package org.thoughtcrime.securesms.crypto;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
|
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.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||||
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.crypto.IdentityKey;
|
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||||
|
@ -129,13 +127,6 @@ public class KeyExchangeProcessorV1 extends KeyExchangeProcessor {
|
||||||
broadcastSecurityUpdateEvent(context, threadId);
|
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,
|
public LocalKeyRecord initializeRecordFor(Context context,
|
||||||
MasterSecret masterSecret,
|
MasterSecret masterSecret,
|
||||||
CanonicalRecipient recipient)
|
CanonicalRecipient recipient)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package org.thoughtcrime.securesms.crypto;
|
package org.thoughtcrime.securesms.crypto;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
|
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.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||||
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.thoughtcrime.securesms.util.TextSecurePreferences;
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ public class EncryptingSmsDatabase extends SmsDatabase {
|
||||||
{
|
{
|
||||||
long type = Types.BASE_INBOX_TYPE;
|
long type = Types.BASE_INBOX_TYPE;
|
||||||
|
|
||||||
if (!message.isSecureMessage()) {
|
if (!message.isSecureMessage() && !message.isEndSession()) {
|
||||||
type |= Types.ENCRYPTION_SYMMETRIC_BIT;
|
type |= Types.ENCRYPTION_SYMMETRIC_BIT;
|
||||||
message = message.withMessageBody(getEncryptedBody(masterSecret, message.getMessageBody()));
|
message = message.withMessageBody(getEncryptedBody(masterSecret, message.getMessageBody()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ public interface MmsSmsColumns {
|
||||||
|
|
||||||
// Secure Message Information
|
// Secure Message Information
|
||||||
protected static final long SECURE_MESSAGE_BIT = 0x800000;
|
protected static final long SECURE_MESSAGE_BIT = 0x800000;
|
||||||
|
protected static final long END_SESSION_BIT = 0x400000;
|
||||||
|
|
||||||
// Encrypted Storage Information
|
// Encrypted Storage Information
|
||||||
protected static final long ENCRYPTION_MASK = 0xFF000000;
|
protected static final long ENCRYPTION_MASK = 0xFF000000;
|
||||||
|
@ -75,6 +76,10 @@ public interface MmsSmsColumns {
|
||||||
return (type & SECURE_MESSAGE_BIT) != 0;
|
return (type & SECURE_MESSAGE_BIT) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isEndSessionType(long type) {
|
||||||
|
return (type & END_SESSION_BIT) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isKeyExchangeType(long type) {
|
public static boolean isKeyExchangeType(long type) {
|
||||||
return (type & KEY_EXCHANGE_BIT) != 0;
|
return (type & KEY_EXCHANGE_BIT) != 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -256,6 +256,10 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||||
} 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;
|
||||||
|
} else if (message.isEndSession()) {
|
||||||
|
type |= Types.END_SESSION_BIT;
|
||||||
|
type |= Types.SECURE_MESSAGE_BIT;
|
||||||
|
type |= Types.ENCRYPTION_REMOTE_BIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
Recipients recipients;
|
Recipients recipients;
|
||||||
|
@ -328,6 +332,7 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||||
protected List<Long> insertMessageOutbox(long threadId, OutgoingTextMessage message, long type) {
|
protected List<Long> insertMessageOutbox(long threadId, OutgoingTextMessage message, long type) {
|
||||||
if (message.isKeyExchange()) type |= Types.KEY_EXCHANGE_BIT;
|
if (message.isKeyExchange()) type |= Types.KEY_EXCHANGE_BIT;
|
||||||
else if (message.isSecureMessage()) type |= Types.SECURE_MESSAGE_BIT;
|
else if (message.isSecureMessage()) type |= Types.SECURE_MESSAGE_BIT;
|
||||||
|
else if (message.isEndSession()) type |= Types.END_SESSION_BIT;
|
||||||
|
|
||||||
long date = System.currentTimeMillis();
|
long date = System.currentTimeMillis();
|
||||||
List<Long> messageIds = new LinkedList<Long>();
|
List<Long> messageIds = new LinkedList<Long>();
|
||||||
|
|
|
@ -84,6 +84,10 @@ public abstract class DisplayRecord {
|
||||||
return SmsDatabase.Types.isKeyExchangeType(type);
|
return SmsDatabase.Types.isKeyExchangeType(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEndSession() {
|
||||||
|
return SmsDatabase.Types.isEndSessionType(type);
|
||||||
|
}
|
||||||
|
|
||||||
public int getGroupAction() {
|
public int getGroupAction() {
|
||||||
return groupAction;
|
return groupAction;
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,9 @@ public class SmsMessageRecord extends MessageRecord {
|
||||||
return emphasisAdded(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session));
|
return emphasisAdded(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session));
|
||||||
} else if (!getBody().isPlaintext()) {
|
} else if (!getBody().isPlaintext()) {
|
||||||
return emphasisAdded(context.getString(R.string.MessageNotifier_encrypted_message));
|
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())) {
|
} else if (isOutgoing() && Tag.isTagged(getBody().getBody())) {
|
||||||
return new SpannableString(Tag.stripTag(getBody().getBody()));
|
return new SpannableString(Tag.stripTag(getBody().getBody()));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -78,6 +78,9 @@ public class ThreadRecord extends DisplayRecord {
|
||||||
return emphasisAdded(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session));
|
return emphasisAdded(context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session));
|
||||||
} else if (!getBody().isPlaintext()) {
|
} else if (!getBody().isPlaintext()) {
|
||||||
return emphasisAdded(context.getString(R.string.MessageNotifier_encrypted_message));
|
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 {
|
} else {
|
||||||
if (Util.isEmpty(getBody().getBody())) {
|
if (Util.isEmpty(getBody().getBody())) {
|
||||||
return new SpannableString(context.getString(R.string.MessageNotifier_no_subject));
|
return new SpannableString(context.getString(R.string.MessageNotifier_no_subject));
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package org.thoughtcrime.securesms.protocol;
|
||||||
|
|
||||||
|
public class EndSessionWirePrefix extends WirePrefix {
|
||||||
|
@Override
|
||||||
|
public String calculatePrefix(String message) {
|
||||||
|
return super.calculateEndSessionPrefix(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -51,6 +51,10 @@ public abstract class WirePrefix {
|
||||||
return verifyPrefix("?TSP", message);
|
return verifyPrefix("?TSP", message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isEndSession(String message) {
|
||||||
|
return verifyPrefix("?TSE", message);
|
||||||
|
}
|
||||||
|
|
||||||
public static String calculateKeyExchangePrefix(String message) {
|
public static String calculateKeyExchangePrefix(String message) {
|
||||||
return calculatePrefix(("?TSK" + message).getBytes(), PREFIX_BYTES);
|
return calculatePrefix(("?TSK" + message).getBytes(), PREFIX_BYTES);
|
||||||
}
|
}
|
||||||
|
@ -63,6 +67,10 @@ public abstract class WirePrefix {
|
||||||
return calculatePrefix(("?TSP" + message).getBytes(), PREFIX_BYTES);
|
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) {
|
private static boolean verifyPrefix(String prefixType, String message) {
|
||||||
if (message.length() <= PREFIX_SIZE)
|
if (message.length() <= PREFIX_SIZE)
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -8,6 +8,7 @@ import android.util.Pair;
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
|
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
|
||||||
|
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
|
||||||
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessorV2;
|
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessorV2;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
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.RecipientFormattingException;
|
||||||
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.IncomingEndSessionMessage;
|
||||||
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
|
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
|
||||||
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
|
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
|
||||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
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.push.PushMessageProtos.PushMessageContent;
|
||||||
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
||||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||||
|
import org.whispersystems.textsecure.storage.Session;
|
||||||
|
|
||||||
import ws.com.google.android.mms.MmsException;
|
import ws.com.google.android.mms.MmsException;
|
||||||
|
|
||||||
|
@ -150,7 +153,10 @@ public class PushReceiver {
|
||||||
try {
|
try {
|
||||||
PushMessageContent messageContent = PushMessageContent.parseFrom(message.getBody());
|
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...");
|
Log.w("PushReceiver", "Received push group message...");
|
||||||
handleReceivedGroupMessage(masterSecret, message, messageContent, secure);
|
handleReceivedGroupMessage(masterSecret, message, messageContent, secure);
|
||||||
} else if (messageContent.getAttachmentsCount() > 0) {
|
} 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<Long, Long> 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,
|
private void handleReceivedMediaMessage(MasterSecret masterSecret,
|
||||||
IncomingPushMessage message,
|
IncomingPushMessage message,
|
||||||
PushMessageContent messageContent,
|
PushMessageContent messageContent,
|
||||||
|
|
|
@ -68,7 +68,8 @@ public class SmsReceiver {
|
||||||
|
|
||||||
if (WirePrefix.isEncryptedMessage(message.getMessageBody()) ||
|
if (WirePrefix.isEncryptedMessage(message.getMessageBody()) ||
|
||||||
WirePrefix.isKeyExchange(message.getMessageBody()) ||
|
WirePrefix.isKeyExchange(message.getMessageBody()) ||
|
||||||
WirePrefix.isPreKeyBundle(message.getMessageBody()))
|
WirePrefix.isPreKeyBundle(message.getMessageBody()) ||
|
||||||
|
WirePrefix.isEndSession(message.getMessageBody()))
|
||||||
{
|
{
|
||||||
return multipartMessageHandler.processPotentialMultipartMessage(message);
|
return multipartMessageHandler.processPotentialMultipartMessage(message);
|
||||||
} else {
|
} else {
|
||||||
|
@ -85,7 +86,7 @@ public class SmsReceiver {
|
||||||
messageAndThreadId.second,
|
messageAndThreadId.second,
|
||||||
message.getSender(), message.getSenderDeviceId(),
|
message.getSender(), message.getSenderDeviceId(),
|
||||||
message.getMessageBody(), message.isSecureMessage(),
|
message.getMessageBody(), message.isSecureMessage(),
|
||||||
message.isKeyExchange());
|
message.isKeyExchange(), message.isEndSession());
|
||||||
}
|
}
|
||||||
|
|
||||||
return messageAndThreadId;
|
return messageAndThreadId;
|
||||||
|
@ -197,10 +198,13 @@ public class SmsReceiver {
|
||||||
if (message.isSecureMessage()) return storeSecureMessage(masterSecret, message);
|
if (message.isSecureMessage()) return storeSecureMessage(masterSecret, message);
|
||||||
else if (message.isPreKeyBundle()) return storePreKeyWhisperMessage(masterSecret, (IncomingPreKeyBundleMessage) message);
|
else if (message.isPreKeyBundle()) return storePreKeyWhisperMessage(masterSecret, (IncomingPreKeyBundleMessage) message);
|
||||||
else if (message.isKeyExchange()) return storeKeyExchangeMessage(masterSecret, (IncomingKeyExchangeMessage) message);
|
else if (message.isKeyExchange()) return storeKeyExchangeMessage(masterSecret, (IncomingKeyExchangeMessage) message);
|
||||||
|
else if (message.isEndSession()) return storeSecureMessage(masterSecret, message);
|
||||||
else return storeStandardMessage(masterSecret, message);
|
else return storeStandardMessage(masterSecret, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleReceiveMessage(MasterSecret masterSecret, Intent intent) {
|
private void handleReceiveMessage(MasterSecret masterSecret, Intent intent) {
|
||||||
|
if (intent.getExtras() == null) return;
|
||||||
|
|
||||||
List<IncomingTextMessage> messagesList = intent.getExtras().getParcelableArrayList("text_messages");
|
List<IncomingTextMessage> messagesList = intent.getExtras().getParcelableArrayList("text_messages");
|
||||||
IncomingTextMessage message = assembleMessageFragments(messagesList);
|
IncomingTextMessage message = assembleMessageFragments(messagesList);
|
||||||
|
|
||||||
|
|
|
@ -21,13 +21,16 @@ import android.app.AlarmManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.telephony.SmsManager;
|
import android.telephony.SmsManager;
|
||||||
import android.telephony.SmsMessage;
|
import android.telephony.SmsMessage;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||||
|
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
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.UniversalTransport;
|
||||||
import org.thoughtcrime.securesms.transport.UntrustedIdentityException;
|
import org.thoughtcrime.securesms.transport.UntrustedIdentityException;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
|
import org.whispersystems.textsecure.storage.Session;
|
||||||
|
|
||||||
public class SmsSender {
|
public class SmsSender {
|
||||||
|
|
||||||
|
@ -109,10 +113,22 @@ public class SmsSender {
|
||||||
Log.w("SMSReceiverService", "Running sent callback: " + messageId);
|
Log.w("SMSReceiverService", "Running sent callback: " + messageId);
|
||||||
|
|
||||||
if (result == Activity.RESULT_OK) {
|
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) {
|
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();
|
unregisterForRadioChanges();
|
||||||
|
@ -161,4 +177,5 @@ public class SmsSender {
|
||||||
null, context, SendReceiveService.class),
|
null, context, SendReceiveService.class),
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT));
|
PendingIntent.FLAG_UPDATE_CURRENT));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -216,6 +216,10 @@ public class IncomingTextMessage implements Parcelable {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEndSession() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isIdentityUpdate() {
|
public boolean isIdentityUpdate() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,8 @@ public class MultipartSmsMessageHandler {
|
||||||
return new IncomingKeyExchangeMessage(message.getBaseMessage(), strippedMessage);
|
return new IncomingKeyExchangeMessage(message.getBaseMessage(), strippedMessage);
|
||||||
} else if (message.getWireType() == MultipartSmsTransportMessage.WIRETYPE_PREKEY) {
|
} else if (message.getWireType() == MultipartSmsTransportMessage.WIRETYPE_PREKEY) {
|
||||||
return new IncomingPreKeyBundleMessage(message.getBaseMessage(), strippedMessage);
|
return new IncomingPreKeyBundleMessage(message.getBaseMessage(), strippedMessage);
|
||||||
|
} else if (message.getWireType() == MultipartSmsTransportMessage.WIRETYPE_END_SESSION) {
|
||||||
|
return new IncomingEndSessionMessage(message.getBaseMessage(), strippedMessage);
|
||||||
} else {
|
} else {
|
||||||
return new IncomingEncryptedMessage(message.getBaseMessage(), strippedMessage);
|
return new IncomingEncryptedMessage(message.getBaseMessage(), strippedMessage);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,14 @@ package org.thoughtcrime.securesms.sms;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.protocol.EndSessionWirePrefix;
|
||||||
import org.thoughtcrime.securesms.protocol.KeyExchangeWirePrefix;
|
import org.thoughtcrime.securesms.protocol.KeyExchangeWirePrefix;
|
||||||
import org.thoughtcrime.securesms.protocol.PrekeyBundleWirePrefix;
|
import org.thoughtcrime.securesms.protocol.PrekeyBundleWirePrefix;
|
||||||
import org.thoughtcrime.securesms.protocol.SecureMessageWirePrefix;
|
import org.thoughtcrime.securesms.protocol.SecureMessageWirePrefix;
|
||||||
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||||
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.Hex;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -21,9 +23,10 @@ public class MultipartSmsTransportMessage {
|
||||||
public static final int MULTI_MESSAGE_MULTIPART_OVERHEAD = 3;
|
public static final int MULTI_MESSAGE_MULTIPART_OVERHEAD = 3;
|
||||||
public static final int FIRST_MULTI_MESSAGE_MULTIPART_OVERHEAD = 2;
|
public static final int FIRST_MULTI_MESSAGE_MULTIPART_OVERHEAD = 2;
|
||||||
|
|
||||||
public static final int WIRETYPE_SECURE = 1;
|
public static final int WIRETYPE_SECURE = 1;
|
||||||
public static final int WIRETYPE_KEY = 2;
|
public static final int WIRETYPE_KEY = 2;
|
||||||
public static final int WIRETYPE_PREKEY = 3;
|
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 VERSION_OFFSET = 0;
|
||||||
private static final int MULTIPART_OFFSET = 1;
|
private static final int MULTIPART_OFFSET = 1;
|
||||||
|
@ -39,6 +42,7 @@ public class MultipartSmsTransportMessage {
|
||||||
|
|
||||||
if (WirePrefix.isEncryptedMessage(message.getMessageBody())) wireType = WIRETYPE_SECURE;
|
if (WirePrefix.isEncryptedMessage(message.getMessageBody())) wireType = WIRETYPE_SECURE;
|
||||||
else if (WirePrefix.isPreKeyBundle(message.getMessageBody())) wireType = WIRETYPE_PREKEY;
|
else if (WirePrefix.isPreKeyBundle(message.getMessageBody())) wireType = WIRETYPE_PREKEY;
|
||||||
|
else if (WirePrefix.isEndSession(message.getMessageBody())) wireType = WIRETYPE_END_SESSION;
|
||||||
else wireType = WIRETYPE_KEY;
|
else wireType = WIRETYPE_KEY;
|
||||||
|
|
||||||
Log.w(TAG, "Decoded message with version: " + getCurrentVersion());
|
Log.w(TAG, "Decoded message with version: " + getCurrentVersion());
|
||||||
|
@ -158,6 +162,7 @@ public class MultipartSmsTransportMessage {
|
||||||
|
|
||||||
if (message.isKeyExchange()) prefix = new KeyExchangeWirePrefix();
|
if (message.isKeyExchange()) prefix = new KeyExchangeWirePrefix();
|
||||||
else if (message.isPreKeyBundle()) prefix = new PrekeyBundleWirePrefix();
|
else if (message.isPreKeyBundle()) prefix = new PrekeyBundleWirePrefix();
|
||||||
|
else if (message.isEndSession()) prefix = new EndSessionWirePrefix();
|
||||||
else prefix = new SecureMessageWirePrefix();
|
else prefix = new SecureMessageWirePrefix();
|
||||||
|
|
||||||
if (count == 1) return getSingleEncoded(decoded, prefix);
|
if (count == 1) return getSingleEncoded(decoded, prefix);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,6 +52,10 @@ public class OutgoingTextMessage {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isEndSession() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isPreKeyBundle() {
|
public boolean isPreKeyBundle() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -61,6 +65,8 @@ public class OutgoingTextMessage {
|
||||||
return new OutgoingEncryptedMessage(record.getIndividualRecipient(), record.getBody().getBody());
|
return new OutgoingEncryptedMessage(record.getIndividualRecipient(), record.getBody().getBody());
|
||||||
} else if (record.isKeyExchange()) {
|
} else if (record.isKeyExchange()) {
|
||||||
return new OutgoingKeyExchangeMessage(record.getIndividualRecipient(), record.getBody().getBody());
|
return new OutgoingKeyExchangeMessage(record.getIndividualRecipient(), record.getBody().getBody());
|
||||||
|
} else if (record.isEndSession()) {
|
||||||
|
return new OutgoingEndSessionMessage(new OutgoingTextMessage(record.getIndividualRecipient(), record.getBody().getBody()));
|
||||||
} else {
|
} else {
|
||||||
return new OutgoingTextMessage(record.getIndividualRecipient(), record.getBody().getBody());
|
return new OutgoingTextMessage(record.getIndividualRecipient(), record.getBody().getBody());
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import android.util.Log;
|
||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
|
||||||
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessorV2;
|
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessorV2;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||||
|
@ -85,12 +86,15 @@ public class PushTransport extends BaseTransport {
|
||||||
Recipient recipient = message.getIndividualRecipient();
|
Recipient recipient = message.getIndividualRecipient();
|
||||||
long threadId = message.getThreadId();
|
long threadId = message.getThreadId();
|
||||||
PushServiceSocket socket = PushServiceSocketFactory.create(context);
|
PushServiceSocket socket = PushServiceSocketFactory.create(context);
|
||||||
byte[] plaintext = PushMessageContent.newBuilder()
|
byte[] plaintext = getPlaintextMessage(message);
|
||||||
.setBody(message.getBody().getBody())
|
|
||||||
.build().toByteArray();
|
|
||||||
|
|
||||||
deliver(socket, recipient, threadId, plaintext);
|
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));
|
context.sendBroadcast(constructSentIntent(context, message.getId(), message.getType(), true));
|
||||||
|
|
||||||
} catch (InvalidNumberException e) {
|
} catch (InvalidNumberException e) {
|
||||||
|
@ -295,6 +299,17 @@ public class PushTransport extends BaseTransport {
|
||||||
return builder.build().toByteArray();
|
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,
|
private OutgoingPushMessageList getEncryptedMessages(PushServiceSocket socket, long threadId,
|
||||||
Recipient recipient, byte[] plaintext)
|
Recipient recipient, byte[] plaintext)
|
||||||
throws IOException, InvalidNumberException, UntrustedIdentityException
|
throws IOException, InvalidNumberException, UntrustedIdentityException
|
||||||
|
@ -352,8 +367,4 @@ public class PushTransport extends BaseTransport {
|
||||||
throw new AssertionError("Unknown ciphertext type: " + message.getType());
|
throw new AssertionError("Unknown ciphertext type: " + message.getType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void destroySessions(Recipient recipient) {
|
|
||||||
SessionRecordV2.deleteAll(context, recipient);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
import org.whispersystems.textsecure.crypto.SessionCipher;
|
import org.whispersystems.textsecure.crypto.SessionCipher;
|
||||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||||
|
import org.whispersystems.textsecure.util.Hex;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@ public class SmsTransport extends BaseTransport {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deliver(SmsMessageRecord message) throws UndeliverableMessageException {
|
public void deliver(SmsMessageRecord message) throws UndeliverableMessageException {
|
||||||
if (message.isSecure() || message.isKeyExchange()) {
|
if (message.isSecure() || message.isKeyExchange() || message.isEndSession()) {
|
||||||
deliverSecureMessage(message);
|
deliverSecureMessage(message);
|
||||||
} else {
|
} else {
|
||||||
deliverPlaintextMessage(message);
|
deliverPlaintextMessage(message);
|
||||||
|
@ -58,7 +59,7 @@ public class SmsTransport extends BaseTransport {
|
||||||
MultipartSmsMessageHandler multipartMessageHandler = new MultipartSmsMessageHandler();
|
MultipartSmsMessageHandler multipartMessageHandler = new MultipartSmsMessageHandler();
|
||||||
OutgoingTextMessage transportMessage = OutgoingTextMessage.from(message);
|
OutgoingTextMessage transportMessage = OutgoingTextMessage.from(message);
|
||||||
|
|
||||||
if (message.isSecure()) {
|
if (message.isSecure() || message.isEndSession()) {
|
||||||
transportMessage = getAsymmetricEncrypt(masterSecret, transportMessage);
|
transportMessage = getAsymmetricEncrypt(masterSecret, transportMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,12 +19,12 @@ package org.thoughtcrime.securesms.util;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.provider.Telephony;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.os.Build;
|
|
||||||
import android.provider.Telephony;
|
|
||||||
|
|
||||||
import org.whispersystems.textsecure.util.InvalidNumberException;
|
import org.whispersystems.textsecure.util.InvalidNumberException;
|
||||||
import org.whispersystems.textsecure.util.PhoneNumberFormatter;
|
import org.whispersystems.textsecure.util.PhoneNumberFormatter;
|
||||||
|
|
Loading…
Add table
Reference in a new issue