Add padding for push messages.

1) Use 'bit padding.'

1) By default, pad at 160 byte increments.
This commit is contained in:
Moxie Marlinspike 2014-07-09 14:36:01 -07:00
parent fcaa3f0d73
commit 07fd17ccda
5 changed files with 77 additions and 7 deletions

View File

@ -168,6 +168,13 @@ public class SessionCipher {
}
WhisperMessage ciphertextMessage = new WhisperMessage(decodedMessage);
if (ciphertextMessage.getMessageVersion() != sessionState.getSessionVersion()) {
throw new InvalidMessageException(String.format("Message version %d, but session version %d",
ciphertextMessage.getMessageVersion(),
sessionState.getSessionVersion()));
}
ECPublicKey theirEphemeral = ciphertextMessage.getSenderEphemeral();
int counter = ciphertextMessage.getCounter();
ChainKey chainKey = getOrCreateChainKey(sessionState, theirEphemeral);

View File

@ -16,20 +16,53 @@
*/
package org.whispersystems.textsecure.push;
import android.util.Log;
import org.whispersystems.textsecure.crypto.TransportDetails;
import org.whispersystems.textsecure.util.Base64;
import java.io.IOException;
public class PushTransportDetails implements TransportDetails {
private final int messageVersion;
public PushTransportDetails(int messageVersion) {
this.messageVersion = messageVersion;
}
@Override
public byte[] getStrippedPaddingMessageBody(byte[] messageWithPadding) {
if (messageVersion < 2) throw new AssertionError("Unknown version: " + messageVersion);
else if (messageVersion == 2) return messageWithPadding;
int paddingStart = 0;
for (int i=messageWithPadding.length-1;i>=0;i--) {
if (messageWithPadding[i] == (byte)0x80) {
paddingStart = i;
break;
} else if (messageWithPadding[i] != (byte)0x00) {
Log.w("PushTransportDetails", "Padding byte is malformed, returning unstripped padding.");
return messageWithPadding;
}
}
byte[] strippedMessage = new byte[messageWithPadding.length - paddingStart];
System.arraycopy(messageWithPadding, 0, strippedMessage, 0, strippedMessage.length);
return strippedMessage;
}
@Override
public byte[] getPaddedMessageBody(byte[] messageBody) {
return messageBody;
if (messageVersion < 2) throw new AssertionError("Unknown version: " + messageVersion);
else if (messageVersion == 2) return messageBody;
byte[] paddedMessage = new byte[getPaddedMessageLength(messageBody.length)];
System.arraycopy(messageBody, 0, paddedMessage, 0, messageBody.length);
paddedMessage[messageBody.length] = (byte)0x80;
return paddedMessage;
}
@Override
@ -41,4 +74,15 @@ public class PushTransportDetails implements TransportDetails {
public byte[] getDecodedMessage(byte[] encodedMessageBytes) throws IOException {
return encodedMessageBytes;
}
private int getPaddedMessageLength(int messageLength) {
int messageLengthWithTerminator = messageLength + 1;
int messagePartCount = messageLengthWithTerminator / 160;
if (messageLengthWithTerminator % 160 != 0) {
messagePartCount++;
}
return messagePartCount * 160;
}
}

View File

@ -7,6 +7,17 @@ import org.whispersystems.textsecure.crypto.MasterSecret;
public class SessionUtil {
public static int getSessionVersion(Context context,
MasterSecret masterSecret,
RecipientDevice recipient)
{
return
new TextSecureSessionStore(context, masterSecret)
.loadSession(recipient.getRecipientId(), recipient.getDeviceId())
.getSessionState()
.getSessionVersion();
}
public static boolean hasEncryptCapableSession(Context context,
MasterSecret masterSecret,
CanonicalRecipient recipient)

View File

@ -52,8 +52,11 @@ import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipherFactory;
import org.whispersystems.textsecure.crypto.TransportDetails;
import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.push.PushTransportDetails;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.SessionUtil;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.Hex;
@ -210,10 +213,12 @@ public class DecryptingQueue {
return;
}
int sessionVersion = SessionUtil.getSessionVersion(context, masterSecret, recipientDevice);
SessionCipher sessionCipher = SessionCipherFactory.getInstance(context, masterSecret, recipientDevice);
byte[] plaintextBody = sessionCipher.decrypt(message.getBody());
TransportDetails transport = new PushTransportDetails(sessionVersion);
message = message.withBody(plaintextBody);
message = message.withBody(transport.getStrippedPaddingMessageBody(plaintextBody));
sendResult(PushReceiver.RESULT_OK);
} catch (InvalidMessageException | LegacyMessageException | RecipientFormattingException e) {
Log.w("DecryptionQueue", e);

View File

@ -52,6 +52,7 @@ import org.whispersystems.textsecure.push.PushAttachmentPointer;
import org.whispersystems.textsecure.push.PushBody;
import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.push.PushTransportDetails;
import org.whispersystems.textsecure.push.StaleDevices;
import org.whispersystems.textsecure.push.StaleDevicesException;
import org.whispersystems.textsecure.push.UnregisteredUserException;
@ -344,8 +345,10 @@ public class PushTransport extends BaseTransport {
}
}
int sessionVersion = SessionUtil.getSessionVersion(context, masterSecret, pushAddress);
SessionCipher cipher = SessionCipherFactory.getInstance(context, masterSecret, pushAddress);
CiphertextMessage message = cipher.encrypt(plaintext);
byte[] paddedPlaintext = new PushTransportDetails(sessionVersion).getPaddedMessageBody(plaintext);
CiphertextMessage message = cipher.encrypt(paddedPlaintext);
int remoteRegistrationId = cipher.getRemoteRegistrationId();
if (message.getType() == CiphertextMessage.PREKEY_TYPE) {