Switch to CBC mode with a derived IV.

1) Since we're not CPU or space constrained (and are in fact
   padding), and since keystream reuse would be more catastrophic
   than IV reuse without chosen plaintext.
This commit is contained in:
Moxie Marlinspike 2014-07-28 20:56:49 -07:00
parent c375ed8638
commit 741171c49f
10 changed files with 243 additions and 46 deletions

View file

@ -19,6 +19,7 @@ message SessionStructure {
optional uint32 index = 1; optional uint32 index = 1;
optional bytes cipherKey = 2; optional bytes cipherKey = 2;
optional bytes macKey = 3; optional bytes macKey = 3;
optional bytes iv = 4;
} }
repeated MessageKey messageKeys = 4; repeated MessageKey messageKeys = 4;

View file

@ -102,7 +102,7 @@ public class SessionCipher {
int previousCounter = sessionState.getPreviousCounter(); int previousCounter = sessionState.getPreviousCounter();
int sessionVersion = sessionState.getSessionVersion(); int sessionVersion = sessionState.getSessionVersion();
byte[] ciphertextBody = getCiphertext(messageKeys, paddedMessage); byte[] ciphertextBody = getCiphertext(sessionVersion, messageKeys, paddedMessage);
CiphertextMessage ciphertextMessage = new WhisperMessage(sessionVersion, messageKeys.getMacKey(), CiphertextMessage ciphertextMessage = new WhisperMessage(sessionVersion, messageKeys.getMacKey(),
senderEphemeral, chainKey.getIndex(), senderEphemeral, chainKey.getIndex(),
previousCounter, ciphertextBody, previousCounter, ciphertextBody,
@ -226,18 +226,19 @@ public class SessionCipher {
sessionState.getSessionVersion())); sessionState.getSessionVersion()));
} }
int messageVersion = ciphertextMessage.getMessageVersion();
ECPublicKey theirEphemeral = ciphertextMessage.getSenderRatchetKey(); ECPublicKey theirEphemeral = ciphertextMessage.getSenderRatchetKey();
int counter = ciphertextMessage.getCounter(); int counter = ciphertextMessage.getCounter();
ChainKey chainKey = getOrCreateChainKey(sessionState, theirEphemeral); ChainKey chainKey = getOrCreateChainKey(sessionState, theirEphemeral);
MessageKeys messageKeys = getOrCreateMessageKeys(sessionState, theirEphemeral, MessageKeys messageKeys = getOrCreateMessageKeys(sessionState, theirEphemeral,
chainKey, counter); chainKey, counter);
ciphertextMessage.verifyMac(ciphertextMessage.getMessageVersion(), ciphertextMessage.verifyMac(messageVersion,
sessionState.getRemoteIdentityKey(), sessionState.getRemoteIdentityKey(),
sessionState.getLocalIdentityKey(), sessionState.getLocalIdentityKey(),
messageKeys.getMacKey()); messageKeys.getMacKey());
byte[] plaintext = getPlaintext(messageKeys, ciphertextMessage.getBody()); byte[] plaintext = getPlaintext(messageVersion, messageKeys, ciphertextMessage.getBody());
sessionState.clearUnacknowledgedPreKeyMessage(); sessionState.clearUnacknowledgedPreKeyMessage();
@ -304,11 +305,15 @@ public class SessionCipher {
return chainKey.getMessageKeys(); return chainKey.getMessageKeys();
} }
private byte[] getCiphertext(MessageKeys messageKeys, byte[] plaintext) { private byte[] getCiphertext(int version, MessageKeys messageKeys, byte[] plaintext) {
try { try {
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, Cipher cipher;
messageKeys.getCipherKey(),
messageKeys.getCounter()); 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); return cipher.doFinal(plaintext);
} catch (IllegalBlockSizeException | BadPaddingException e) { } 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 { try {
Cipher cipher = getCipher(Cipher.DECRYPT_MODE, Cipher cipher;
messageKeys.getCipherKey(),
messageKeys.getCounter()); 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); return cipher.doFinal(cipherText);
} catch (IllegalBlockSizeException | BadPaddingException e) { } catch (IllegalBlockSizeException | BadPaddingException e) {
throw new AssertionError(e); throw new AssertionError(e);
@ -344,4 +354,16 @@ public class SessionCipher {
throw new AssertionError(e); 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);
}
}
} }

View file

@ -19,22 +19,32 @@ package org.whispersystems.libaxolotl.kdf;
import org.whispersystems.libaxolotl.util.ByteUtil; import org.whispersystems.libaxolotl.util.ByteUtil;
import java.text.ParseException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
public class DerivedMessageSecrets { 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 CIPHER_KEY_LENGTH = 32;
private static final int MAC_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 cipherKey;
private final SecretKeySpec macKey; private final SecretKeySpec macKey;
private final IvParameterSpec iv;
public DerivedMessageSecrets(byte[] okm) { 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.cipherKey = new SecretKeySpec(keys[0], "AES");
this.macKey = new SecretKeySpec(keys[1], "HmacSHA256"); this.macKey = new SecretKeySpec(keys[1], "HmacSHA256");
this.iv = new IvParameterSpec(keys[2]);
} catch (ParseException e) {
throw new AssertionError(e);
}
} }
public SecretKeySpec getCipherKey() { public SecretKeySpec getCipherKey() {
@ -44,4 +54,8 @@ public class DerivedMessageSecrets {
public SecretKeySpec getMacKey() { public SecretKeySpec getMacKey() {
return macKey; return macKey;
} }
public IvParameterSpec getIv() {
return iv;
}
} }

View file

@ -59,7 +59,7 @@ public class ChainKey {
byte[] keyMaterialBytes = kdf.deriveSecrets(inputKeyMaterial, "WhisperMessageKeys".getBytes(), DerivedMessageSecrets.SIZE); byte[] keyMaterialBytes = kdf.deriveSecrets(inputKeyMaterial, "WhisperMessageKeys".getBytes(), DerivedMessageSecrets.SIZE);
DerivedMessageSecrets keyMaterial = new DerivedMessageSecrets(keyMaterialBytes); 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) { private byte[] getBaseMaterial(byte[] seed) {

View file

@ -16,17 +16,20 @@
*/ */
package org.whispersystems.libaxolotl.ratchet; package org.whispersystems.libaxolotl.ratchet;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
public class MessageKeys { public class MessageKeys {
private final SecretKeySpec cipherKey; private final SecretKeySpec cipherKey;
private final SecretKeySpec macKey; private final SecretKeySpec macKey;
private final int counter; 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.cipherKey = cipherKey;
this.macKey = macKey; this.macKey = macKey;
this.iv = iv;
this.counter = counter; this.counter = counter;
} }
@ -38,6 +41,10 @@ public class MessageKeys {
return macKey; return macKey;
} }
public IvParameterSpec getIv() {
return iv;
}
public int getCounter() { public int getCounter() {
return counter; return counter;
} }

View file

@ -41,6 +41,7 @@ import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import static org.whispersystems.libaxolotl.state.StorageProtos.SessionStructure; import static org.whispersystems.libaxolotl.state.StorageProtos.SessionStructure;
@ -298,6 +299,7 @@ public class SessionState {
if (messageKey.getIndex() == counter) { if (messageKey.getIndex() == counter) {
result = new MessageKeys(new SecretKeySpec(messageKey.getCipherKey().toByteArray(), "AES"), result = new MessageKeys(new SecretKeySpec(messageKey.getCipherKey().toByteArray(), "AES"),
new SecretKeySpec(messageKey.getMacKey().toByteArray(), "HmacSHA256"), new SecretKeySpec(messageKey.getMacKey().toByteArray(), "HmacSHA256"),
new IvParameterSpec(messageKey.getIv().toByteArray()),
messageKey.getIndex()); messageKey.getIndex());
messageKeyIterator.remove(); messageKeyIterator.remove();
@ -323,6 +325,7 @@ public class SessionState {
.setCipherKey(ByteString.copyFrom(messageKeys.getCipherKey().getEncoded())) .setCipherKey(ByteString.copyFrom(messageKeys.getCipherKey().getEncoded()))
.setMacKey(ByteString.copyFrom(messageKeys.getMacKey().getEncoded())) .setMacKey(ByteString.copyFrom(messageKeys.getMacKey().getEncoded()))
.setIndex(messageKeys.getCounter()) .setIndex(messageKeys.getCounter())
.setIv(ByteString.copyFrom(messageKeys.getIv().getIV()))
.build(); .build();
Chain updatedChain = chain.toBuilder() Chain updatedChain = chain.toBuilder()

View file

@ -1051,6 +1051,16 @@ public final class StorageProtos {
* <code>optional bytes macKey = 3;</code> * <code>optional bytes macKey = 3;</code>
*/ */
com.google.protobuf.ByteString getMacKey(); com.google.protobuf.ByteString getMacKey();
// optional bytes iv = 4;
/**
* <code>optional bytes iv = 4;</code>
*/
boolean hasIv();
/**
* <code>optional bytes iv = 4;</code>
*/
com.google.protobuf.ByteString getIv();
} }
/** /**
* Protobuf type {@code textsecure.SessionStructure.Chain.MessageKey} * Protobuf type {@code textsecure.SessionStructure.Chain.MessageKey}
@ -1118,6 +1128,11 @@ public final class StorageProtos {
macKey_ = input.readBytes(); macKey_ = input.readBytes();
break; break;
} }
case 34: {
bitField0_ |= 0x00000008;
iv_ = input.readBytes();
break;
}
} }
} }
} catch (com.google.protobuf.InvalidProtocolBufferException e) { } catch (com.google.protobuf.InvalidProtocolBufferException e) {
@ -1206,10 +1221,27 @@ public final class StorageProtos {
return macKey_; return macKey_;
} }
// optional bytes iv = 4;
public static final int IV_FIELD_NUMBER = 4;
private com.google.protobuf.ByteString iv_;
/**
* <code>optional bytes iv = 4;</code>
*/
public boolean hasIv() {
return ((bitField0_ & 0x00000008) == 0x00000008);
}
/**
* <code>optional bytes iv = 4;</code>
*/
public com.google.protobuf.ByteString getIv() {
return iv_;
}
private void initFields() { private void initFields() {
index_ = 0; index_ = 0;
cipherKey_ = com.google.protobuf.ByteString.EMPTY; cipherKey_ = com.google.protobuf.ByteString.EMPTY;
macKey_ = com.google.protobuf.ByteString.EMPTY; macKey_ = com.google.protobuf.ByteString.EMPTY;
iv_ = com.google.protobuf.ByteString.EMPTY;
} }
private byte memoizedIsInitialized = -1; private byte memoizedIsInitialized = -1;
public final boolean isInitialized() { public final boolean isInitialized() {
@ -1232,6 +1264,9 @@ public final class StorageProtos {
if (((bitField0_ & 0x00000004) == 0x00000004)) { if (((bitField0_ & 0x00000004) == 0x00000004)) {
output.writeBytes(3, macKey_); output.writeBytes(3, macKey_);
} }
if (((bitField0_ & 0x00000008) == 0x00000008)) {
output.writeBytes(4, iv_);
}
getUnknownFields().writeTo(output); getUnknownFields().writeTo(output);
} }
@ -1253,6 +1288,10 @@ public final class StorageProtos {
size += com.google.protobuf.CodedOutputStream size += com.google.protobuf.CodedOutputStream
.computeBytesSize(3, macKey_); .computeBytesSize(3, macKey_);
} }
if (((bitField0_ & 0x00000008) == 0x00000008)) {
size += com.google.protobuf.CodedOutputStream
.computeBytesSize(4, iv_);
}
size += getUnknownFields().getSerializedSize(); size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size; memoizedSerializedSize = size;
return size; return size;
@ -1375,6 +1414,8 @@ public final class StorageProtos {
bitField0_ = (bitField0_ & ~0x00000002); bitField0_ = (bitField0_ & ~0x00000002);
macKey_ = com.google.protobuf.ByteString.EMPTY; macKey_ = com.google.protobuf.ByteString.EMPTY;
bitField0_ = (bitField0_ & ~0x00000004); bitField0_ = (bitField0_ & ~0x00000004);
iv_ = com.google.protobuf.ByteString.EMPTY;
bitField0_ = (bitField0_ & ~0x00000008);
return this; return this;
} }
@ -1415,6 +1456,10 @@ public final class StorageProtos {
to_bitField0_ |= 0x00000004; to_bitField0_ |= 0x00000004;
} }
result.macKey_ = macKey_; result.macKey_ = macKey_;
if (((from_bitField0_ & 0x00000008) == 0x00000008)) {
to_bitField0_ |= 0x00000008;
}
result.iv_ = iv_;
result.bitField0_ = to_bitField0_; result.bitField0_ = to_bitField0_;
onBuilt(); onBuilt();
return result; return result;
@ -1440,6 +1485,9 @@ public final class StorageProtos {
if (other.hasMacKey()) { if (other.hasMacKey()) {
setMacKey(other.getMacKey()); setMacKey(other.getMacKey());
} }
if (other.hasIv()) {
setIv(other.getIv());
}
this.mergeUnknownFields(other.getUnknownFields()); this.mergeUnknownFields(other.getUnknownFields());
return this; return this;
} }
@ -1572,6 +1620,42 @@ public final class StorageProtos {
return this; return this;
} }
// optional bytes iv = 4;
private com.google.protobuf.ByteString iv_ = com.google.protobuf.ByteString.EMPTY;
/**
* <code>optional bytes iv = 4;</code>
*/
public boolean hasIv() {
return ((bitField0_ & 0x00000008) == 0x00000008);
}
/**
* <code>optional bytes iv = 4;</code>
*/
public com.google.protobuf.ByteString getIv() {
return iv_;
}
/**
* <code>optional bytes iv = 4;</code>
*/
public Builder setIv(com.google.protobuf.ByteString value) {
if (value == null) {
throw new NullPointerException();
}
bitField0_ |= 0x00000008;
iv_ = value;
onChanged();
return this;
}
/**
* <code>optional bytes iv = 4;</code>
*/
public Builder clearIv() {
bitField0_ = (bitField0_ & ~0x00000008);
iv_ = getDefaultInstance().getIv();
onChanged();
return this;
}
// @@protoc_insertion_point(builder_scope:textsecure.SessionStructure.Chain.MessageKey) // @@protoc_insertion_point(builder_scope:textsecure.SessionStructure.Chain.MessageKey)
} }
@ -8249,7 +8333,7 @@ public final class StorageProtos {
static { static {
java.lang.String[] descriptorData = { java.lang.String[] descriptorData = {
"\n\032LocalStorageProtocol.proto\022\ntextsecure" + "\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" + "\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(" + "moteIdentityPublic\030\003 \001(\014\022\017\n\007rootKey\030\004 \001(" +
"\014\022\027\n\017previousCounter\030\005 \001(\r\0227\n\013senderChai" + "\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" + "\001(\0132*.textsecure.SessionStructure.Pendin" +
"gPreKey\022\034\n\024remoteRegistrationId\030\n \001(\r\022\033\n" + "gPreKey\022\034\n\024remoteRegistrationId\030\n \001(\r\022\033\n" +
"\023localRegistrationId\030\013 \001(\r\022\024\n\014needsRefre" + "\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" + "\022\030\n\020senderRatchetKey\030\001 \001(\014\022\037\n\027senderRatc" +
"hetKeyPrivate\030\002 \001(\014\022=\n\010chainKey\030\003 \001(\0132+." + "hetKeyPrivate\030\002 \001(\014\022=\n\010chainKey\030\003 \001(\0132+." +
"textsecure.SessionStructure.Chain.ChainK" + "textsecure.SessionStructure.Chain.ChainK" +
"ey\022B\n\013messageKeys\030\004 \003(\0132-.textsecure.Ses" + "ey\022B\n\013messageKeys\030\004 \003(\0132-.textsecure.Ses" +
"sionStructure.Chain.MessageKey\032&\n\010ChainK" + "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" + "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" + "\n\006macKey\030\003 \001(\014\022\n\n\002iv\030\004 \001(\014\032\315\001\n\022PendingKe" +
"\010sequence\030\001 \001(\r\022\024\n\014localBaseKey\030\002 \001(\014\022\033\n" + "yExchange\022\020\n\010sequence\030\001 \001(\r\022\024\n\014localBase" +
"\023localBaseKeyPrivate\030\003 \001(\014\022\027\n\017localRatch" + "Key\030\002 \001(\014\022\033\n\023localBaseKeyPrivate\030\003 \001(\014\022\027" +
"etKey\030\004 \001(\014\022\036\n\026localRatchetKeyPrivate\030\005 " + "\n\017localRatchetKey\030\004 \001(\014\022\036\n\026localRatchetK" +
"\001(\014\022\030\n\020localIdentityKey\030\007 \001(\014\022\037\n\027localId" + "eyPrivate\030\005 \001(\014\022\030\n\020localIdentityKey\030\007 \001(" +
"entityKeyPrivate\030\010 \001(\014\032J\n\rPendingPreKey\022" + "\014\022\037\n\027localIdentityKeyPrivate\030\010 \001(\014\032J\n\rPe" +
"\020\n\010preKeyId\030\001 \001(\r\022\026\n\016signedPreKeyId\030\003 \001(" + "ndingPreKey\022\020\n\010preKeyId\030\001 \001(\r\022\026\n\016signedP" +
"\005\022\017\n\007baseKey\030\002 \001(\014\"\177\n\017RecordStructure\0224\n" + "reKeyId\030\003 \001(\005\022\017\n\007baseKey\030\002 \001(\014\"\177\n\017Record" +
"\016currentSession\030\001 \001(\0132\034.textsecure.Sessi", "Structure\0224\n\016currentSession\030\001 \001(\0132\034.text",
"onStructure\0226\n\020previousSessions\030\002 \003(\0132\034." + "secure.SessionStructure\0226\n\020previousSessi" +
"textsecure.SessionStructure\"J\n\025PreKeyRec" + "ons\030\002 \003(\0132\034.textsecure.SessionStructure\"" +
"ordStructure\022\n\n\002id\030\001 \001(\r\022\021\n\tpublicKey\030\002 " + "J\n\025PreKeyRecordStructure\022\n\n\002id\030\001 \001(\r\022\021\n\t" +
"\001(\014\022\022\n\nprivateKey\030\003 \001(\014\"v\n\033SignedPreKeyR" + "publicKey\030\002 \001(\014\022\022\n\nprivateKey\030\003 \001(\014\"v\n\033S" +
"ecordStructure\022\n\n\002id\030\001 \001(\r\022\021\n\tpublicKey\030" + "ignedPreKeyRecordStructure\022\n\n\002id\030\001 \001(\r\022\021" +
"\002 \001(\014\022\022\n\nprivateKey\030\003 \001(\014\022\021\n\tsignature\030\004" + "\n\tpublicKey\030\002 \001(\014\022\022\n\nprivateKey\030\003 \001(\014\022\021\n" +
" \001(\014\022\021\n\ttimestamp\030\005 \001(\006\"A\n\030IdentityKeyPa" + "\tsignature\030\004 \001(\014\022\021\n\ttimestamp\030\005 \001(\006\"A\n\030I" +
"irStructure\022\021\n\tpublicKey\030\001 \001(\014\022\022\n\nprivat" + "dentityKeyPairStructure\022\021\n\tpublicKey\030\001 \001" +
"eKey\030\002 \001(\014B4\n#org.whispersystems.libaxol" + "(\014\022\022\n\nprivateKey\030\002 \001(\014B4\n#org.whispersys" +
"otl.stateB\rStorageProtos" "tems.libaxolotl.stateB\rStorageProtos"
}; };
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() {
@ -8317,7 +8401,7 @@ public final class StorageProtos {
internal_static_textsecure_SessionStructure_Chain_MessageKey_fieldAccessorTable = new internal_static_textsecure_SessionStructure_Chain_MessageKey_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable( com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_textsecure_SessionStructure_Chain_MessageKey_descriptor, 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_PendingKeyExchange_descriptor =
internal_static_textsecure_SessionStructure_descriptor.getNestedTypes().get(1); internal_static_textsecure_SessionStructure_descriptor.getNestedTypes().get(1);
internal_static_textsecure_SessionStructure_PendingKeyExchange_fieldAccessorTable = new internal_static_textsecure_SessionStructure_PendingKeyExchange_fieldAccessorTable = new

View file

@ -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));
}
}

View file

@ -48,6 +48,12 @@ android {
assets.srcDirs = ['assets'] assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs'] jniLibs.srcDirs = ['libs']
} }
androidTest {
java.srcDirs = ['androidTest/java']
resources.srcDirs = ['androidTest/java']
aidl.srcDirs = ['androidTest/java']
renderscript.srcDirs = ['androidTest/java']
}
} }
} }
} }

View file

@ -58,7 +58,10 @@ public class PushTransportDetails implements TransportDetails {
if (messageVersion < 2) throw new AssertionError("Unknown version: " + messageVersion); if (messageVersion < 2) throw new AssertionError("Unknown version: " + messageVersion);
else if (messageVersion == 2) return messageBody; 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); System.arraycopy(messageBody, 0, paddedMessage, 0, messageBody.length);
paddedMessage[messageBody.length] = (byte)0x80; paddedMessage[messageBody.length] = (byte)0x80;