diff --git a/libaxolotl/protobuf/LocalStorageProtocol.proto b/libaxolotl/protobuf/LocalStorageProtocol.proto
index 93badb59e3..388533d93a 100644
--- a/libaxolotl/protobuf/LocalStorageProtocol.proto
+++ b/libaxolotl/protobuf/LocalStorageProtocol.proto
@@ -19,6 +19,7 @@ message SessionStructure {
optional uint32 index = 1;
optional bytes cipherKey = 2;
optional bytes macKey = 3;
+ optional bytes iv = 4;
}
repeated MessageKey messageKeys = 4;
diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java
index 33415ce573..90b0a0ddfb 100644
--- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java
+++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java
@@ -102,7 +102,7 @@ public class SessionCipher {
int previousCounter = sessionState.getPreviousCounter();
int sessionVersion = sessionState.getSessionVersion();
- byte[] ciphertextBody = getCiphertext(messageKeys, paddedMessage);
+ byte[] ciphertextBody = getCiphertext(sessionVersion, messageKeys, paddedMessage);
CiphertextMessage ciphertextMessage = new WhisperMessage(sessionVersion, messageKeys.getMacKey(),
senderEphemeral, chainKey.getIndex(),
previousCounter, ciphertextBody,
@@ -226,18 +226,19 @@ public class SessionCipher {
sessionState.getSessionVersion()));
}
+ int messageVersion = ciphertextMessage.getMessageVersion();
ECPublicKey theirEphemeral = ciphertextMessage.getSenderRatchetKey();
int counter = ciphertextMessage.getCounter();
ChainKey chainKey = getOrCreateChainKey(sessionState, theirEphemeral);
MessageKeys messageKeys = getOrCreateMessageKeys(sessionState, theirEphemeral,
chainKey, counter);
- ciphertextMessage.verifyMac(ciphertextMessage.getMessageVersion(),
+ ciphertextMessage.verifyMac(messageVersion,
sessionState.getRemoteIdentityKey(),
sessionState.getLocalIdentityKey(),
messageKeys.getMacKey());
- byte[] plaintext = getPlaintext(messageKeys, ciphertextMessage.getBody());
+ byte[] plaintext = getPlaintext(messageVersion, messageKeys, ciphertextMessage.getBody());
sessionState.clearUnacknowledgedPreKeyMessage();
@@ -304,11 +305,15 @@ public class SessionCipher {
return chainKey.getMessageKeys();
}
- private byte[] getCiphertext(MessageKeys messageKeys, byte[] plaintext) {
+ private byte[] getCiphertext(int version, MessageKeys messageKeys, byte[] plaintext) {
try {
- Cipher cipher = getCipher(Cipher.ENCRYPT_MODE,
- messageKeys.getCipherKey(),
- messageKeys.getCounter());
+ Cipher cipher;
+
+ if (version >= 3) {
+ cipher = getCipher(Cipher.ENCRYPT_MODE, messageKeys.getCipherKey(), messageKeys.getIv());
+ } else {
+ cipher = getCipher(Cipher.ENCRYPT_MODE, messageKeys.getCipherKey(), messageKeys.getCounter());
+ }
return cipher.doFinal(plaintext);
} catch (IllegalBlockSizeException | BadPaddingException e) {
@@ -316,11 +321,16 @@ public class SessionCipher {
}
}
- private byte[] getPlaintext(MessageKeys messageKeys, byte[] cipherText) {
+ private byte[] getPlaintext(int version, MessageKeys messageKeys, byte[] cipherText) {
try {
- Cipher cipher = getCipher(Cipher.DECRYPT_MODE,
- messageKeys.getCipherKey(),
- messageKeys.getCounter());
+ Cipher cipher;
+
+ if (version >= 3) {
+ cipher = getCipher(Cipher.DECRYPT_MODE, messageKeys.getCipherKey(), messageKeys.getIv());
+ } else {
+ cipher = getCipher(Cipher.DECRYPT_MODE, messageKeys.getCipherKey(), messageKeys.getCounter());
+ }
+
return cipher.doFinal(cipherText);
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new AssertionError(e);
@@ -344,4 +354,16 @@ public class SessionCipher {
throw new AssertionError(e);
}
}
+
+ private Cipher getCipher(int mode, SecretKeySpec key, IvParameterSpec iv) {
+ try {
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+ cipher.init(mode, key, iv);
+ return cipher;
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException | java.security.InvalidKeyException |
+ InvalidAlgorithmParameterException e)
+ {
+ throw new AssertionError(e);
+ }
+ }
}
\ No newline at end of file
diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/kdf/DerivedMessageSecrets.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/kdf/DerivedMessageSecrets.java
index f5f782d7ea..b3f54fb809 100644
--- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/kdf/DerivedMessageSecrets.java
+++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/kdf/DerivedMessageSecrets.java
@@ -19,22 +19,32 @@ package org.whispersystems.libaxolotl.kdf;
import org.whispersystems.libaxolotl.util.ByteUtil;
+import java.text.ParseException;
+
+import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class DerivedMessageSecrets {
- public static final int SIZE = 64;
+ public static final int SIZE = 80;
private static final int CIPHER_KEY_LENGTH = 32;
private static final int MAC_KEY_LENGTH = 32;
+ private static final int IV_LENGTH = 16;
- private final SecretKeySpec cipherKey;
- private final SecretKeySpec macKey;
+ private final SecretKeySpec cipherKey;
+ private final SecretKeySpec macKey;
+ private final IvParameterSpec iv;
public DerivedMessageSecrets(byte[] okm) {
- byte[][] keys = ByteUtil.split(okm, CIPHER_KEY_LENGTH, MAC_KEY_LENGTH);
+ try {
+ byte[][] keys = ByteUtil.split(okm, CIPHER_KEY_LENGTH, MAC_KEY_LENGTH, IV_LENGTH);
- this.cipherKey = new SecretKeySpec(keys[0], "AES");
- this.macKey = new SecretKeySpec(keys[1], "HmacSHA256");
+ this.cipherKey = new SecretKeySpec(keys[0], "AES");
+ this.macKey = new SecretKeySpec(keys[1], "HmacSHA256");
+ this.iv = new IvParameterSpec(keys[2]);
+ } catch (ParseException e) {
+ throw new AssertionError(e);
+ }
}
public SecretKeySpec getCipherKey() {
@@ -44,4 +54,8 @@ public class DerivedMessageSecrets {
public SecretKeySpec getMacKey() {
return macKey;
}
+
+ public IvParameterSpec getIv() {
+ return iv;
+ }
}
diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/ChainKey.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/ChainKey.java
index ebc38eda8b..9dd1dbee6e 100644
--- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/ChainKey.java
+++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/ChainKey.java
@@ -59,7 +59,7 @@ public class ChainKey {
byte[] keyMaterialBytes = kdf.deriveSecrets(inputKeyMaterial, "WhisperMessageKeys".getBytes(), DerivedMessageSecrets.SIZE);
DerivedMessageSecrets keyMaterial = new DerivedMessageSecrets(keyMaterialBytes);
- return new MessageKeys(keyMaterial.getCipherKey(), keyMaterial.getMacKey(), index);
+ return new MessageKeys(keyMaterial.getCipherKey(), keyMaterial.getMacKey(), keyMaterial.getIv(), index);
}
private byte[] getBaseMaterial(byte[] seed) {
diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/MessageKeys.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/MessageKeys.java
index e4aed3df7e..95a8c7dc08 100644
--- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/MessageKeys.java
+++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/ratchet/MessageKeys.java
@@ -16,17 +16,20 @@
*/
package org.whispersystems.libaxolotl.ratchet;
+import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class MessageKeys {
- private final SecretKeySpec cipherKey;
- private final SecretKeySpec macKey;
- private final int counter;
+ private final SecretKeySpec cipherKey;
+ private final SecretKeySpec macKey;
+ private final IvParameterSpec iv;
+ private final int counter;
- public MessageKeys(SecretKeySpec cipherKey, SecretKeySpec macKey, int counter) {
+ public MessageKeys(SecretKeySpec cipherKey, SecretKeySpec macKey, IvParameterSpec iv, int counter) {
this.cipherKey = cipherKey;
this.macKey = macKey;
+ this.iv = iv;
this.counter = counter;
}
@@ -38,6 +41,10 @@ public class MessageKeys {
return macKey;
}
+ public IvParameterSpec getIv() {
+ return iv;
+ }
+
public int getCounter() {
return counter;
}
diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionState.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionState.java
index bd150ca2bc..f6bc90bc26 100644
--- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionState.java
+++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/SessionState.java
@@ -41,6 +41,7 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import static org.whispersystems.libaxolotl.state.StorageProtos.SessionStructure;
@@ -298,6 +299,7 @@ public class SessionState {
if (messageKey.getIndex() == counter) {
result = new MessageKeys(new SecretKeySpec(messageKey.getCipherKey().toByteArray(), "AES"),
new SecretKeySpec(messageKey.getMacKey().toByteArray(), "HmacSHA256"),
+ new IvParameterSpec(messageKey.getIv().toByteArray()),
messageKey.getIndex());
messageKeyIterator.remove();
@@ -323,6 +325,7 @@ public class SessionState {
.setCipherKey(ByteString.copyFrom(messageKeys.getCipherKey().getEncoded()))
.setMacKey(ByteString.copyFrom(messageKeys.getMacKey().getEncoded()))
.setIndex(messageKeys.getCounter())
+ .setIv(ByteString.copyFrom(messageKeys.getIv().getIV()))
.build();
Chain updatedChain = chain.toBuilder()
diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/StorageProtos.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/StorageProtos.java
index 17d14ae444..14792928be 100644
--- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/StorageProtos.java
+++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/state/StorageProtos.java
@@ -1051,6 +1051,16 @@ public final class StorageProtos {
* optional bytes macKey = 3;
*/
com.google.protobuf.ByteString getMacKey();
+
+ // optional bytes iv = 4;
+ /**
+ * optional bytes iv = 4;
+ */
+ boolean hasIv();
+ /**
+ * optional bytes iv = 4;
+ */
+ com.google.protobuf.ByteString getIv();
}
/**
* Protobuf type {@code textsecure.SessionStructure.Chain.MessageKey}
@@ -1118,6 +1128,11 @@ public final class StorageProtos {
macKey_ = input.readBytes();
break;
}
+ case 34: {
+ bitField0_ |= 0x00000008;
+ iv_ = input.readBytes();
+ break;
+ }
}
}
} catch (com.google.protobuf.InvalidProtocolBufferException e) {
@@ -1206,10 +1221,27 @@ public final class StorageProtos {
return macKey_;
}
+ // optional bytes iv = 4;
+ public static final int IV_FIELD_NUMBER = 4;
+ private com.google.protobuf.ByteString iv_;
+ /**
+ * optional bytes iv = 4;
+ */
+ public boolean hasIv() {
+ return ((bitField0_ & 0x00000008) == 0x00000008);
+ }
+ /**
+ * optional bytes iv = 4;
+ */
+ public com.google.protobuf.ByteString getIv() {
+ return iv_;
+ }
+
private void initFields() {
index_ = 0;
cipherKey_ = com.google.protobuf.ByteString.EMPTY;
macKey_ = com.google.protobuf.ByteString.EMPTY;
+ iv_ = com.google.protobuf.ByteString.EMPTY;
}
private byte memoizedIsInitialized = -1;
public final boolean isInitialized() {
@@ -1232,6 +1264,9 @@ public final class StorageProtos {
if (((bitField0_ & 0x00000004) == 0x00000004)) {
output.writeBytes(3, macKey_);
}
+ if (((bitField0_ & 0x00000008) == 0x00000008)) {
+ output.writeBytes(4, iv_);
+ }
getUnknownFields().writeTo(output);
}
@@ -1253,6 +1288,10 @@ public final class StorageProtos {
size += com.google.protobuf.CodedOutputStream
.computeBytesSize(3, macKey_);
}
+ if (((bitField0_ & 0x00000008) == 0x00000008)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeBytesSize(4, iv_);
+ }
size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size;
return size;
@@ -1375,6 +1414,8 @@ public final class StorageProtos {
bitField0_ = (bitField0_ & ~0x00000002);
macKey_ = com.google.protobuf.ByteString.EMPTY;
bitField0_ = (bitField0_ & ~0x00000004);
+ iv_ = com.google.protobuf.ByteString.EMPTY;
+ bitField0_ = (bitField0_ & ~0x00000008);
return this;
}
@@ -1415,6 +1456,10 @@ public final class StorageProtos {
to_bitField0_ |= 0x00000004;
}
result.macKey_ = macKey_;
+ if (((from_bitField0_ & 0x00000008) == 0x00000008)) {
+ to_bitField0_ |= 0x00000008;
+ }
+ result.iv_ = iv_;
result.bitField0_ = to_bitField0_;
onBuilt();
return result;
@@ -1440,6 +1485,9 @@ public final class StorageProtos {
if (other.hasMacKey()) {
setMacKey(other.getMacKey());
}
+ if (other.hasIv()) {
+ setIv(other.getIv());
+ }
this.mergeUnknownFields(other.getUnknownFields());
return this;
}
@@ -1572,6 +1620,42 @@ public final class StorageProtos {
return this;
}
+ // optional bytes iv = 4;
+ private com.google.protobuf.ByteString iv_ = com.google.protobuf.ByteString.EMPTY;
+ /**
+ * optional bytes iv = 4;
+ */
+ public boolean hasIv() {
+ return ((bitField0_ & 0x00000008) == 0x00000008);
+ }
+ /**
+ * optional bytes iv = 4;
+ */
+ public com.google.protobuf.ByteString getIv() {
+ return iv_;
+ }
+ /**
+ * optional bytes iv = 4;
+ */
+ public Builder setIv(com.google.protobuf.ByteString value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ bitField0_ |= 0x00000008;
+ iv_ = value;
+ onChanged();
+ return this;
+ }
+ /**
+ * optional bytes iv = 4;
+ */
+ public Builder clearIv() {
+ bitField0_ = (bitField0_ & ~0x00000008);
+ iv_ = getDefaultInstance().getIv();
+ onChanged();
+ return this;
+ }
+
// @@protoc_insertion_point(builder_scope:textsecure.SessionStructure.Chain.MessageKey)
}
@@ -8249,7 +8333,7 @@ public final class StorageProtos {
static {
java.lang.String[] descriptorData = {
"\n\032LocalStorageProtocol.proto\022\ntextsecure" +
- "\"\307\010\n\020SessionStructure\022\026\n\016sessionVersion\030" +
+ "\"\323\010\n\020SessionStructure\022\026\n\016sessionVersion\030" +
"\001 \001(\r\022\033\n\023localIdentityPublic\030\002 \001(\014\022\034\n\024re" +
"moteIdentityPublic\030\003 \001(\014\022\017\n\007rootKey\030\004 \001(" +
"\014\022\027\n\017previousCounter\030\005 \001(\r\0227\n\013senderChai" +
@@ -8261,33 +8345,33 @@ public final class StorageProtos {
"\001(\0132*.textsecure.SessionStructure.Pendin" +
"gPreKey\022\034\n\024remoteRegistrationId\030\n \001(\r\022\033\n" +
"\023localRegistrationId\030\013 \001(\r\022\024\n\014needsRefre" +
- "sh\030\014 \001(\010\022\024\n\014aliceBaseKey\030\r \001(\014\032\255\002\n\005Chain" +
+ "sh\030\014 \001(\010\022\024\n\014aliceBaseKey\030\r \001(\014\032\271\002\n\005Chain" +
"\022\030\n\020senderRatchetKey\030\001 \001(\014\022\037\n\027senderRatc" +
"hetKeyPrivate\030\002 \001(\014\022=\n\010chainKey\030\003 \001(\0132+." +
"textsecure.SessionStructure.Chain.ChainK" +
"ey\022B\n\013messageKeys\030\004 \003(\0132-.textsecure.Ses" +
"sionStructure.Chain.MessageKey\032&\n\010ChainK" +
- "ey\022\r\n\005index\030\001 \001(\r\022\013\n\003key\030\002 \001(\014\032>\n\nMessag",
+ "ey\022\r\n\005index\030\001 \001(\r\022\013\n\003key\030\002 \001(\014\032J\n\nMessag",
"eKey\022\r\n\005index\030\001 \001(\r\022\021\n\tcipherKey\030\002 \001(\014\022\016" +
- "\n\006macKey\030\003 \001(\014\032\315\001\n\022PendingKeyExchange\022\020\n" +
- "\010sequence\030\001 \001(\r\022\024\n\014localBaseKey\030\002 \001(\014\022\033\n" +
- "\023localBaseKeyPrivate\030\003 \001(\014\022\027\n\017localRatch" +
- "etKey\030\004 \001(\014\022\036\n\026localRatchetKeyPrivate\030\005 " +
- "\001(\014\022\030\n\020localIdentityKey\030\007 \001(\014\022\037\n\027localId" +
- "entityKeyPrivate\030\010 \001(\014\032J\n\rPendingPreKey\022" +
- "\020\n\010preKeyId\030\001 \001(\r\022\026\n\016signedPreKeyId\030\003 \001(" +
- "\005\022\017\n\007baseKey\030\002 \001(\014\"\177\n\017RecordStructure\0224\n" +
- "\016currentSession\030\001 \001(\0132\034.textsecure.Sessi",
- "onStructure\0226\n\020previousSessions\030\002 \003(\0132\034." +
- "textsecure.SessionStructure\"J\n\025PreKeyRec" +
- "ordStructure\022\n\n\002id\030\001 \001(\r\022\021\n\tpublicKey\030\002 " +
- "\001(\014\022\022\n\nprivateKey\030\003 \001(\014\"v\n\033SignedPreKeyR" +
- "ecordStructure\022\n\n\002id\030\001 \001(\r\022\021\n\tpublicKey\030" +
- "\002 \001(\014\022\022\n\nprivateKey\030\003 \001(\014\022\021\n\tsignature\030\004" +
- " \001(\014\022\021\n\ttimestamp\030\005 \001(\006\"A\n\030IdentityKeyPa" +
- "irStructure\022\021\n\tpublicKey\030\001 \001(\014\022\022\n\nprivat" +
- "eKey\030\002 \001(\014B4\n#org.whispersystems.libaxol" +
- "otl.stateB\rStorageProtos"
+ "\n\006macKey\030\003 \001(\014\022\n\n\002iv\030\004 \001(\014\032\315\001\n\022PendingKe" +
+ "yExchange\022\020\n\010sequence\030\001 \001(\r\022\024\n\014localBase" +
+ "Key\030\002 \001(\014\022\033\n\023localBaseKeyPrivate\030\003 \001(\014\022\027" +
+ "\n\017localRatchetKey\030\004 \001(\014\022\036\n\026localRatchetK" +
+ "eyPrivate\030\005 \001(\014\022\030\n\020localIdentityKey\030\007 \001(" +
+ "\014\022\037\n\027localIdentityKeyPrivate\030\010 \001(\014\032J\n\rPe" +
+ "ndingPreKey\022\020\n\010preKeyId\030\001 \001(\r\022\026\n\016signedP" +
+ "reKeyId\030\003 \001(\005\022\017\n\007baseKey\030\002 \001(\014\"\177\n\017Record" +
+ "Structure\0224\n\016currentSession\030\001 \001(\0132\034.text",
+ "secure.SessionStructure\0226\n\020previousSessi" +
+ "ons\030\002 \003(\0132\034.textsecure.SessionStructure\"" +
+ "J\n\025PreKeyRecordStructure\022\n\n\002id\030\001 \001(\r\022\021\n\t" +
+ "publicKey\030\002 \001(\014\022\022\n\nprivateKey\030\003 \001(\014\"v\n\033S" +
+ "ignedPreKeyRecordStructure\022\n\n\002id\030\001 \001(\r\022\021" +
+ "\n\tpublicKey\030\002 \001(\014\022\022\n\nprivateKey\030\003 \001(\014\022\021\n" +
+ "\tsignature\030\004 \001(\014\022\021\n\ttimestamp\030\005 \001(\006\"A\n\030I" +
+ "dentityKeyPairStructure\022\021\n\tpublicKey\030\001 \001" +
+ "(\014\022\022\n\nprivateKey\030\002 \001(\014B4\n#org.whispersys" +
+ "tems.libaxolotl.stateB\rStorageProtos"
};
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
@@ -8317,7 +8401,7 @@ public final class StorageProtos {
internal_static_textsecure_SessionStructure_Chain_MessageKey_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_textsecure_SessionStructure_Chain_MessageKey_descriptor,
- new java.lang.String[] { "Index", "CipherKey", "MacKey", });
+ new java.lang.String[] { "Index", "CipherKey", "MacKey", "Iv", });
internal_static_textsecure_SessionStructure_PendingKeyExchange_descriptor =
internal_static_textsecure_SessionStructure_descriptor.getNestedTypes().get(1);
internal_static_textsecure_SessionStructure_PendingKeyExchange_fieldAccessorTable = new
diff --git a/library/androidTest/java/org/whispersystems/textsecure/push/PushTransportDetailsTest.java b/library/androidTest/java/org/whispersystems/textsecure/push/PushTransportDetailsTest.java
new file mode 100644
index 0000000000..137352c78d
--- /dev/null
+++ b/library/androidTest/java/org/whispersystems/textsecure/push/PushTransportDetailsTest.java
@@ -0,0 +1,57 @@
+package org.whispersystems.textsecure.push;
+
+import android.test.AndroidTestCase;
+import android.util.Base64;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+public class PushTransportDetailsTest extends AndroidTestCase {
+
+ private final PushTransportDetails transportV2 = new PushTransportDetails(2);
+ private final PushTransportDetails transportV3 = new PushTransportDetails(3);
+
+ public void testV3Padding() {
+ for (int i=0;i<159;i++) {
+ byte[] message = new byte[i];
+ assertEquals(transportV3.getPaddedMessageBody(message).length, 159);
+ }
+
+ for (int i=159;i<319;i++) {
+ byte[] message = new byte[i];
+ assertEquals(transportV3.getPaddedMessageBody(message).length, 319);
+ }
+
+ for (int i=319;i<479;i++) {
+ byte[] message = new byte[i];
+ assertEquals(transportV3.getPaddedMessageBody(message).length, 479);
+ }
+ }
+
+ public void testV2Padding() {
+ for (int i=0;i<480;i++) {
+ byte[] message = new byte[i];
+ assertTrue(transportV2.getPaddedMessageBody(message).length == message.length);
+ }
+ }
+
+ public void testV3Encoding() throws NoSuchAlgorithmException {
+ byte[] message = new byte[501];
+ SecureRandom.getInstance("SHA1PRNG").nextBytes(message);
+
+ byte[] padded = transportV3.getEncodedMessage(message);
+
+ assertTrue(Arrays.equals(padded, message));
+ }
+
+ public void testV2Encoding() throws NoSuchAlgorithmException {
+ byte[] message = new byte[501];
+ SecureRandom.getInstance("SHA1PRNG").nextBytes(message);
+
+ byte[] padded = transportV2.getEncodedMessage(message);
+
+ assertTrue(Arrays.equals(padded, message));
+ }
+
+}
diff --git a/library/build.gradle b/library/build.gradle
index 832ef5d353..70792c51c5 100644
--- a/library/build.gradle
+++ b/library/build.gradle
@@ -48,6 +48,12 @@ android {
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
+ androidTest {
+ java.srcDirs = ['androidTest/java']
+ resources.srcDirs = ['androidTest/java']
+ aidl.srcDirs = ['androidTest/java']
+ renderscript.srcDirs = ['androidTest/java']
+ }
}
}
}
diff --git a/library/src/org/whispersystems/textsecure/push/PushTransportDetails.java b/library/src/org/whispersystems/textsecure/push/PushTransportDetails.java
index 71cb292dc8..55f03bd729 100644
--- a/library/src/org/whispersystems/textsecure/push/PushTransportDetails.java
+++ b/library/src/org/whispersystems/textsecure/push/PushTransportDetails.java
@@ -58,7 +58,10 @@ public class PushTransportDetails implements TransportDetails {
if (messageVersion < 2) throw new AssertionError("Unknown version: " + messageVersion);
else if (messageVersion == 2) return messageBody;
- byte[] paddedMessage = new byte[getPaddedMessageLength(messageBody.length)];
+ // NOTE: This is dumb. We have our own padding scheme, but so does the cipher.
+ // The +1 -1 here is to make sure the Cipher has room to add one padding byte,
+ // otherwise it'll add a full 16 extra bytes.
+ byte[] paddedMessage = new byte[getPaddedMessageLength(messageBody.length + 1) - 1];
System.arraycopy(messageBody, 0, paddedMessage, 0, messageBody.length);
paddedMessage[messageBody.length] = (byte)0x80;