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); 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(); ECPublicKey theirEphemeral = ciphertextMessage.getSenderEphemeral();
int counter = ciphertextMessage.getCounter(); int counter = ciphertextMessage.getCounter();
ChainKey chainKey = getOrCreateChainKey(sessionState, theirEphemeral); ChainKey chainKey = getOrCreateChainKey(sessionState, theirEphemeral);

View File

@ -16,20 +16,53 @@
*/ */
package org.whispersystems.textsecure.push; package org.whispersystems.textsecure.push;
import android.util.Log;
import org.whispersystems.textsecure.crypto.TransportDetails; import org.whispersystems.textsecure.crypto.TransportDetails;
import org.whispersystems.textsecure.util.Base64;
import java.io.IOException; import java.io.IOException;
public class PushTransportDetails implements TransportDetails { public class PushTransportDetails implements TransportDetails {
private final int messageVersion;
public PushTransportDetails(int messageVersion) {
this.messageVersion = messageVersion;
}
@Override @Override
public byte[] getStrippedPaddingMessageBody(byte[] messageWithPadding) { public byte[] getStrippedPaddingMessageBody(byte[] messageWithPadding) {
return 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 @Override
public byte[] getPaddedMessageBody(byte[] messageBody) { 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 @Override
@ -41,4 +74,15 @@ public class PushTransportDetails implements TransportDetails {
public byte[] getDecodedMessage(byte[] encodedMessageBytes) throws IOException { public byte[] getDecodedMessage(byte[] encodedMessageBytes) throws IOException {
return encodedMessageBytes; 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 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, public static boolean hasEncryptCapableSession(Context context,
MasterSecret masterSecret, MasterSecret masterSecret,
CanonicalRecipient recipient) CanonicalRecipient recipient)

View File

@ -52,8 +52,11 @@ import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import org.whispersystems.libaxolotl.state.SessionStore; import org.whispersystems.libaxolotl.state.SessionStore;
import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipherFactory; import org.whispersystems.textsecure.crypto.SessionCipherFactory;
import org.whispersystems.textsecure.crypto.TransportDetails;
import org.whispersystems.textsecure.push.IncomingPushMessage; import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.push.PushTransportDetails;
import org.whispersystems.textsecure.storage.RecipientDevice; import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.SessionUtil;
import org.whispersystems.textsecure.storage.TextSecureSessionStore; import org.whispersystems.textsecure.storage.TextSecureSessionStore;
import org.whispersystems.textsecure.util.Base64; import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.Hex; import org.whispersystems.textsecure.util.Hex;
@ -210,10 +213,12 @@ public class DecryptingQueue {
return; return;
} }
SessionCipher sessionCipher = SessionCipherFactory.getInstance(context, masterSecret, recipientDevice); int sessionVersion = SessionUtil.getSessionVersion(context, masterSecret, recipientDevice);
byte[] plaintextBody = sessionCipher.decrypt(message.getBody()); 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); sendResult(PushReceiver.RESULT_OK);
} catch (InvalidMessageException | LegacyMessageException | RecipientFormattingException e) { } catch (InvalidMessageException | LegacyMessageException | RecipientFormattingException e) {
Log.w("DecryptionQueue", 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.PushBody;
import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent; import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.push.PushTransportDetails;
import org.whispersystems.textsecure.push.StaleDevices; import org.whispersystems.textsecure.push.StaleDevices;
import org.whispersystems.textsecure.push.StaleDevicesException; import org.whispersystems.textsecure.push.StaleDevicesException;
import org.whispersystems.textsecure.push.UnregisteredUserException; 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); 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(); int remoteRegistrationId = cipher.getRemoteRegistrationId();
if (message.getType() == CiphertextMessage.PREKEY_TYPE) { if (message.getType() == CiphertextMessage.PREKEY_TYPE) {