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;