From 07fd17ccda5b378a9359f5d4eecead7d2cc8a6ea Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Wed, 9 Jul 2014 14:36:01 -0700 Subject: [PATCH] Add padding for push messages. 1) Use 'bit padding.' 1) By default, pad at 160 byte increments. --- .../libaxolotl/SessionCipher.java | 7 +++ .../textsecure/push/PushTransportDetails.java | 50 +++++++++++++++++-- .../textsecure/storage/SessionUtil.java | 11 ++++ .../securesms/crypto/DecryptingQueue.java | 11 ++-- .../securesms/transport/PushTransport.java | 5 +- 5 files changed, 77 insertions(+), 7 deletions(-) diff --git a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java index cb69a5d55f..47b87bcf78 100644 --- a/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java +++ b/libaxolotl/src/main/java/org/whispersystems/libaxolotl/SessionCipher.java @@ -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); diff --git a/library/src/org/whispersystems/textsecure/push/PushTransportDetails.java b/library/src/org/whispersystems/textsecure/push/PushTransportDetails.java index f8a87d5539..0a10e4bb20 100644 --- a/library/src/org/whispersystems/textsecure/push/PushTransportDetails.java +++ b/library/src/org/whispersystems/textsecure/push/PushTransportDetails.java @@ -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) { - 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 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; + } } diff --git a/library/src/org/whispersystems/textsecure/storage/SessionUtil.java b/library/src/org/whispersystems/textsecure/storage/SessionUtil.java index 984af341f4..5d92d617b7 100644 --- a/library/src/org/whispersystems/textsecure/storage/SessionUtil.java +++ b/library/src/org/whispersystems/textsecure/storage/SessionUtil.java @@ -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) diff --git a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java index ab2f361be6..a6997be629 100644 --- a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java +++ b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java @@ -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; } - SessionCipher sessionCipher = SessionCipherFactory.getInstance(context, masterSecret, recipientDevice); - byte[] plaintextBody = sessionCipher.decrypt(message.getBody()); + 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); diff --git a/src/org/thoughtcrime/securesms/transport/PushTransport.java b/src/org/thoughtcrime/securesms/transport/PushTransport.java index a20230779d..d15682616a 100644 --- a/src/org/thoughtcrime/securesms/transport/PushTransport.java +++ b/src/org/thoughtcrime/securesms/transport/PushTransport.java @@ -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) {