From dbc070cd6580a63ac627f2bac655273438999a8a Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Thu, 31 Oct 2013 17:23:45 -0700 Subject: [PATCH] Refactor the ciphertext message parsing and building. --- .../textsecure/crypto/MessageCipher.java | 126 +++------------ .../textsecure/crypto/MessageMac.java | 41 ++--- .../textsecure/crypto/SessionCipher.java | 19 +-- .../crypto/protocol/CiphertextMessage.java | 144 ++++++++++++++++++ .../crypto/protocol/PreKeyBundleMessage.java | 95 +++++------- .../textsecure/push/PushBody.java | 20 +++ .../textsecure/push/PushServiceSocket.java | 17 +-- .../securesms/ReceiveKeyActivity.java | 9 +- .../securesms/crypto/DecryptingQueue.java | 32 ++-- .../crypto/KeyExchangeProcessor.java | 27 ++-- .../crypto/protocol/KeyExchangeMessage.java | 12 +- .../securesms/service/PushReceiver.java | 4 +- .../securesms/service/SmsReceiver.java | 4 +- .../securesms/sms/SmsTransportDetails.java | 12 +- .../securesms/transport/MmsTransport.java | 13 +- .../securesms/transport/PushTransport.java | 71 ++++----- .../securesms/transport/SmsTransport.java | 37 +++-- 17 files changed, 365 insertions(+), 318 deletions(-) create mode 100644 library/src/org/whispersystems/textsecure/crypto/protocol/CiphertextMessage.java create mode 100644 library/src/org/whispersystems/textsecure/push/PushBody.java diff --git a/library/src/org/whispersystems/textsecure/crypto/MessageCipher.java b/library/src/org/whispersystems/textsecure/crypto/MessageCipher.java index 6f45267773..b1b654e684 100644 --- a/library/src/org/whispersystems/textsecure/crypto/MessageCipher.java +++ b/library/src/org/whispersystems/textsecure/crypto/MessageCipher.java @@ -19,11 +19,8 @@ package org.whispersystems.textsecure.crypto; import android.content.Context; import org.whispersystems.textsecure.crypto.SessionCipher.SessionCipherContext; +import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.storage.CanonicalRecipientAddress; -import org.whispersystems.textsecure.util.Conversions; - -import java.io.IOException; -import java.nio.ByteBuffer; /** * Parses and serializes the encrypted message format. @@ -33,48 +30,23 @@ import java.nio.ByteBuffer; public class MessageCipher { - public static final int SUPPORTED_VERSION = 2; - public static final int CRADLE_AGREEMENT_VERSION = 2; - - public static final int VERSION_LENGTH = 1; - private static final int SENDER_KEY_ID_LENGTH = 3; - private static final int RECEIVER_KEY_ID_LENGTH = 3; - public static final int NEXT_KEY_LENGTH = PublicKey.KEY_SIZE; - private static final int COUNTER_LENGTH = 3; - public static final int HEADER_LENGTH = VERSION_LENGTH + SENDER_KEY_ID_LENGTH + RECEIVER_KEY_ID_LENGTH + COUNTER_LENGTH + NEXT_KEY_LENGTH; - - public static final int VERSION_OFFSET = 0; - private static final int SENDER_KEY_ID_OFFSET = VERSION_OFFSET + VERSION_LENGTH; - public static final int RECEIVER_KEY_ID_OFFSET = SENDER_KEY_ID_OFFSET + SENDER_KEY_ID_LENGTH; - public static final int NEXT_KEY_OFFSET = RECEIVER_KEY_ID_OFFSET + RECEIVER_KEY_ID_LENGTH; - private static final int COUNTER_OFFSET = NEXT_KEY_OFFSET + NEXT_KEY_LENGTH; - private static final int TEXT_OFFSET = COUNTER_OFFSET + COUNTER_LENGTH; - private final Context context; private final MasterSecret masterSecret; private final IdentityKeyPair localIdentityKey; - private final TransportDetails transportDetails; - public MessageCipher(Context context, MasterSecret masterSecret, - IdentityKeyPair localIdentityKey, - TransportDetails transportDetails) - { + public MessageCipher(Context context, MasterSecret masterSecret, IdentityKeyPair localIdentityKey) { this.context = context.getApplicationContext(); this.masterSecret = masterSecret; this.localIdentityKey = localIdentityKey; - this.transportDetails = transportDetails; } - public byte[] encrypt(CanonicalRecipientAddress recipient, byte[] plaintext) { + public CiphertextMessage encrypt(CanonicalRecipientAddress recipient, byte[] paddedBody) { synchronized (SessionCipher.CIPHER_LOCK) { - byte[] paddedBody = transportDetails.getPaddedMessageBody(plaintext); - SessionCipher sessionCipher = new SessionCipher(); - SessionCipherContext sessionContext = sessionCipher.getEncryptionContext(context, masterSecret, localIdentityKey, recipient); - byte[] ciphertextBody = sessionCipher.encrypt(sessionContext, paddedBody); - byte[] formattedCiphertext = getFormattedCiphertext(sessionContext, ciphertextBody); - byte[] ciphertextMessage = sessionCipher.mac(sessionContext, formattedCiphertext); + SessionCipher sessionCipher = new SessionCipher(); + SessionCipherContext sessionContext = sessionCipher.getEncryptionContext(context, masterSecret, localIdentityKey, recipient); + byte[] ciphertextBody = sessionCipher.encrypt(sessionContext, paddedBody); - return transportDetails.getEncodedMessage(ciphertextMessage); + return new CiphertextMessage(sessionContext, ciphertextBody); } } @@ -83,25 +55,17 @@ public class MessageCipher { { synchronized (SessionCipher.CIPHER_LOCK) { try { - byte[] decodedMessage = transportDetails.getDecodedMessage(ciphertext); - - if (decodedMessage.length <= HEADER_LENGTH) { - throw new InvalidMessageException("Message is shorter than headers"); - } - - int messageVersion = getMessageVersion(decodedMessage); + CiphertextMessage message = new CiphertextMessage(ciphertext); - if (messageVersion > SUPPORTED_VERSION) { - throw new InvalidMessageException("Unsupported version: " + messageVersion); - } + int messageVersion = message.getCurrentVersion(); + int supportedVersion = message.getSupportedVersion(); + int negotiatedVersion = Math.min(supportedVersion, CiphertextMessage.SUPPORTED_VERSION); + int senderKeyId = message.getSenderKeyId(); + int receiverKeyId = message.getReceiverKeyId(); + PublicKey nextRemoteKey = new PublicKey(message.getNextKeyBytes()); + int counter = message.getCounter(); + byte[] body = message.getBody(); - int supportedVersion = getSupportedVersion(decodedMessage); - int receiverKeyId = getReceiverKeyId(decodedMessage); - int senderKeyId = getSenderKeyId(decodedMessage); - int counter = getCiphertextCounter(decodedMessage); - byte[] ciphertextBody = getMessageBody(decodedMessage); - PublicKey nextRemoteKey = getNextRemoteKey(decodedMessage); - int negotiatedVersion = Math.min(supportedVersion, SUPPORTED_VERSION); SessionCipher sessionCipher = new SessionCipher(); SessionCipherContext sessionContext = sessionCipher.getDecryptionContext(context, masterSecret, localIdentityKey, @@ -112,67 +76,13 @@ public class MessageCipher { messageVersion, negotiatedVersion); - sessionCipher.verifyMac(sessionContext, decodedMessage); + message.verifyMac(sessionContext); - byte[] plaintextWithPadding = sessionCipher.decrypt(sessionContext, ciphertextBody); - return transportDetails.getStrippedPaddingMessageBody(plaintextWithPadding); - } catch (IOException e) { - throw new InvalidMessageException(e); + return sessionCipher.decrypt(sessionContext, body); } catch (InvalidKeyException e) { throw new InvalidMessageException(e); } } } - private byte[] getFormattedCiphertext(SessionCipherContext sessionContext, byte[] ciphertextBody) { - ByteBuffer buffer = ByteBuffer.allocate(HEADER_LENGTH + ciphertextBody.length); - byte versionByte = Conversions.intsToByteHighAndLow(sessionContext.getNegotiatedVersion(), SUPPORTED_VERSION); - byte[] senderKeyIdBytes = Conversions.mediumToByteArray(sessionContext.getSenderKeyId()); - byte[] receiverKeyIdBytes = Conversions.mediumToByteArray(sessionContext.getRecipientKeyId()); - byte[] nextKeyBytes = sessionContext.getNextKey().serialize(); - byte[] counterBytes = Conversions.mediumToByteArray(sessionContext.getCounter()); - - buffer.put(versionByte); - buffer.put(senderKeyIdBytes); - buffer.put(receiverKeyIdBytes); - buffer.put(nextKeyBytes); - buffer.put(counterBytes); - buffer.put(ciphertextBody); - - return buffer.array(); - } - - private int getMessageVersion(byte[] message) { - return Conversions.highBitsToInt(message[VERSION_OFFSET]); - } - - private int getSupportedVersion(byte[] message) { - return Conversions.lowBitsToInt(message[VERSION_OFFSET]); - } - - private int getSenderKeyId(byte[] message) { - return Conversions.byteArrayToMedium(message, SENDER_KEY_ID_OFFSET); - } - - private int getReceiverKeyId(byte[] message) { - return Conversions.byteArrayToMedium(message, RECEIVER_KEY_ID_OFFSET); - } - - private int getCiphertextCounter(byte[] message) { - return Conversions.byteArrayToMedium(message, COUNTER_OFFSET); - } - - private byte[] getMessageBody(byte[] message) { - byte[] body = new byte[message.length - HEADER_LENGTH - MessageMac.MAC_LENGTH]; - System.arraycopy(message, TEXT_OFFSET, body, 0, body.length); - - return body; - } - - private PublicKey getNextRemoteKey(byte[] message) throws InvalidKeyException { - byte[] key = new byte[NEXT_KEY_LENGTH]; - System.arraycopy(message, NEXT_KEY_OFFSET, key, 0, key.length); - - return new PublicKey(key); - } } diff --git a/library/src/org/whispersystems/textsecure/crypto/MessageMac.java b/library/src/org/whispersystems/textsecure/crypto/MessageMac.java index 4e237c88bd..88b8506aa3 100644 --- a/library/src/org/whispersystems/textsecure/crypto/MessageMac.java +++ b/library/src/org/whispersystems/textsecure/crypto/MessageMac.java @@ -31,7 +31,7 @@ public class MessageMac { public static final int MAC_LENGTH = 10; - private static byte[] calculateMac(byte[] message, int offset, int length, SecretKeySpec macKey) { + public static byte[] calculateMac(byte[] message, int offset, int length, SecretKeySpec macKey) { try { Mac mac = Mac.getInstance("HmacSHA1"); mac.init(macKey); @@ -50,36 +50,19 @@ public class MessageMac { throw new IllegalArgumentException(e); } } - - public static byte[] buildMessageWithMac(byte[] message, SecretKeySpec macKey) { - byte[] macBytes = calculateMac(message, 0, message.length, macKey); - byte[] combined = new byte[macBytes.length + message.length]; - System.arraycopy(message, 0, combined, 0, message.length); - System.arraycopy(macBytes, 0, combined, message.length, macBytes.length); - - return combined; - } - - public static byte[] getMessageWithoutMac(byte[] message) throws InvalidMacException { - if (message == null || message.length <= MAC_LENGTH) - throw new InvalidMacException("Message shorter than MAC!"); - - byte[] strippedMessage = new byte[message.length - MAC_LENGTH]; - System.arraycopy(message, 0, strippedMessage, 0, strippedMessage.length); - return strippedMessage; - } - - public static void verifyMac(byte[] message, SecretKeySpec macKey) throws InvalidMacException { - byte[] localMacBytes = calculateMac(message, 0, message.length - MAC_LENGTH, macKey); - byte[] receivedMacBytes = new byte[MAC_LENGTH]; - - System.arraycopy(message, message.length-MAC_LENGTH, receivedMacBytes, 0, receivedMacBytes.length); - - Log.w("mm", "Local Mac: " + Hex.toString(localMacBytes)); - Log.w("mm", "Remot Mac: " + Hex.toString(receivedMacBytes)); + + public static void verifyMac(byte[] message, int offset, int length, + byte[] receivedMac, SecretKeySpec macKey) + throws InvalidMacException + { + byte[] localMac = calculateMac(message, offset, length, macKey); + + Log.w("MessageMac", "Local Mac: " + Hex.toString(localMac)); + Log.w("MessageMac", "Remot Mac: " + Hex.toString(receivedMac)); - if (!Arrays.equals(localMacBytes, receivedMacBytes)) + if (!Arrays.equals(localMac, receivedMac)) { throw new InvalidMacException("MAC on message does not match calculated MAC."); + } } } diff --git a/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java b/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java index 54ae1ff9d1..f8757b27c5 100644 --- a/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java +++ b/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java @@ -20,6 +20,7 @@ import android.content.Context; import android.util.Log; import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.storage.CanonicalRecipientAddress; import org.whispersystems.textsecure.storage.InvalidKeyIdException; import org.whispersystems.textsecure.storage.LocalKeyRecord; @@ -55,8 +56,6 @@ public class SessionCipher { public static final int CIPHER_KEY_LENGTH = 16; public static final int MAC_KEY_LENGTH = 20; - - public static final int ENCRYPTED_MESSAGE_OVERHEAD = MessageCipher.HEADER_LENGTH + MessageMac.MAC_LENGTH; public SessionCipherContext getEncryptionContext(Context context, MasterSecret masterSecret, @@ -149,20 +148,6 @@ public class SessionCipher { } } - public byte[] mac(SessionCipherContext context, byte[] formattedCiphertext) { - return MessageMac.buildMessageWithMac(formattedCiphertext, context.getSessionKey().getMacKey()); - } - - public void verifyMac(SessionCipherContext context, byte[] decodedCiphertext) - throws InvalidMessageException - { - try { - MessageMac.verifyMac(decodedCiphertext, context.getSessionKey().getMacKey()); - } catch (InvalidMacException e) { - throw new InvalidMessageException(e); - } - } - private SecretKeySpec deriveMacSecret(SecretKeySpec key) { try { MessageDigest md = MessageDigest.getInstance("SHA-1"); @@ -314,7 +299,7 @@ public class SessionCipher { IdentityKey remoteIdentityKey = records.getSessionRecord().getIdentityKey(); if (isInitiallyExchangedKeys(records, localKeyId, remoteKeyId) && - messageVersion >= MessageCipher.CRADLE_AGREEMENT_VERSION) + messageVersion >= CiphertextMessage.CRADLE_AGREEMENT_VERSION) { return SharedSecretCalculator.calculateSharedSecret(localKeyPair, localIdentityKey, remoteKey, remoteIdentityKey); diff --git a/library/src/org/whispersystems/textsecure/crypto/protocol/CiphertextMessage.java b/library/src/org/whispersystems/textsecure/crypto/protocol/CiphertextMessage.java new file mode 100644 index 0000000000..5db9cbfc80 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/crypto/protocol/CiphertextMessage.java @@ -0,0 +1,144 @@ +package org.whispersystems.textsecure.crypto.protocol; + +import org.whispersystems.textsecure.crypto.InvalidMacException; +import org.whispersystems.textsecure.crypto.InvalidMessageException; +import org.whispersystems.textsecure.crypto.MessageMac; +import org.whispersystems.textsecure.crypto.PublicKey; +import org.whispersystems.textsecure.crypto.SessionCipher; +import org.whispersystems.textsecure.util.Conversions; + +public class CiphertextMessage { + + public static final int SUPPORTED_VERSION = 2; + public static final int CRADLE_AGREEMENT_VERSION = 2; + + static final int VERSION_LENGTH = 1; + private static final int SENDER_KEY_ID_LENGTH = 3; + private static final int RECEIVER_KEY_ID_LENGTH = 3; + private static final int NEXT_KEY_LENGTH = PublicKey.KEY_SIZE; + private static final int COUNTER_LENGTH = 3; + private static final int HEADER_LENGTH = VERSION_LENGTH + SENDER_KEY_ID_LENGTH + + RECEIVER_KEY_ID_LENGTH + COUNTER_LENGTH + + NEXT_KEY_LENGTH; + + static final int VERSION_OFFSET = 0; + private static final int SENDER_KEY_ID_OFFSET = VERSION_OFFSET + VERSION_LENGTH; + private static final int RECEIVER_KEY_ID_OFFSET = SENDER_KEY_ID_OFFSET + SENDER_KEY_ID_LENGTH; + private static final int NEXT_KEY_OFFSET = RECEIVER_KEY_ID_OFFSET + RECEIVER_KEY_ID_LENGTH; + private static final int COUNTER_OFFSET = NEXT_KEY_OFFSET + NEXT_KEY_LENGTH; + private static final int BODY_OFFSET = COUNTER_OFFSET + COUNTER_LENGTH; + + public static final int ENCRYPTED_MESSAGE_OVERHEAD = HEADER_LENGTH + MessageMac.MAC_LENGTH; + + private final byte[] ciphertext; + + public CiphertextMessage(SessionCipher.SessionCipherContext sessionContext, byte[] ciphertextBody) { + this.ciphertext = new byte[HEADER_LENGTH + ciphertextBody.length + MessageMac.MAC_LENGTH]; + setVersion(sessionContext.getMessageVersion(), SUPPORTED_VERSION); + setSenderKeyId(sessionContext.getSenderKeyId()); + setReceiverKeyId(sessionContext.getRecipientKeyId()); + setNextKeyBytes(sessionContext.getNextKey().serialize()); + setCounter(sessionContext.getCounter()); + setBody(ciphertextBody); + setMac(MessageMac.calculateMac(ciphertext, 0, ciphertext.length - MessageMac.MAC_LENGTH, + sessionContext.getSessionKey().getMacKey())); + } + + public CiphertextMessage(byte[] ciphertext) throws InvalidMessageException { + this.ciphertext = ciphertext; + + if (ciphertext.length < HEADER_LENGTH) { + throw new InvalidMessageException("Not long enough for ciphertext header!"); + } + + if (getCurrentVersion() > SUPPORTED_VERSION) { + throw new InvalidMessageException("Unspported version: " + getCurrentVersion()); + } + } + + public void setVersion(int current, int supported) { + ciphertext[VERSION_OFFSET] = Conversions.intsToByteHighAndLow(current, supported); + } + + public int getCurrentVersion() { + return Conversions.highBitsToInt(ciphertext[VERSION_OFFSET]); + } + + public int getSupportedVersion() { + return Conversions.lowBitsToInt(ciphertext[VERSION_OFFSET]); + } + + public void setSenderKeyId(int senderKeyId) { + Conversions.mediumToByteArray(ciphertext, SENDER_KEY_ID_OFFSET, senderKeyId); + } + + public int getSenderKeyId() { + return Conversions.byteArrayToMedium(ciphertext, SENDER_KEY_ID_OFFSET); + } + + public void setReceiverKeyId(int receiverKeyId) { + Conversions.mediumToByteArray(ciphertext, RECEIVER_KEY_ID_OFFSET, receiverKeyId); + } + + public int getReceiverKeyId() { + return Conversions.byteArrayToMedium(ciphertext, RECEIVER_KEY_ID_OFFSET); + } + + public void setNextKeyBytes(byte[] nextKey) { + assert(nextKey.length == NEXT_KEY_LENGTH); + System.arraycopy(nextKey, 0, ciphertext, NEXT_KEY_OFFSET, nextKey.length); + } + + public byte[] getNextKeyBytes() { + byte[] nextKeyBytes = new byte[NEXT_KEY_LENGTH]; + System.arraycopy(ciphertext, NEXT_KEY_OFFSET, nextKeyBytes, 0, nextKeyBytes.length); + + return nextKeyBytes; + } + + public void setCounter(int counter) { + Conversions.mediumToByteArray(ciphertext, COUNTER_OFFSET, counter); + } + + public int getCounter() { + return Conversions.byteArrayToMedium(ciphertext, COUNTER_OFFSET); + } + + public void setBody(byte[] body) { + System.arraycopy(body, 0, ciphertext, BODY_OFFSET, body.length); + } + + public byte[] getBody() { + byte[] body = new byte[ciphertext.length - HEADER_LENGTH - MessageMac.MAC_LENGTH]; + System.arraycopy(ciphertext, BODY_OFFSET, body, 0, body.length); + + return body; + } + + public void setMac(byte[] mac) { + System.arraycopy(mac, 0, ciphertext, ciphertext.length-mac.length, mac.length); + } + + public byte[] getMac() { + byte[] mac = new byte[MessageMac.MAC_LENGTH]; + System.arraycopy(ciphertext, ciphertext.length-mac.length, mac, 0, mac.length); + + return mac; + } + + public byte[] serialize() { + return ciphertext; + } + + public void verifyMac(SessionCipher.SessionCipherContext sessionContext) + throws InvalidMessageException + { + try { + MessageMac.verifyMac(this.ciphertext, 0, this.ciphertext.length - MessageMac.MAC_LENGTH, + getMac(), sessionContext.getSessionKey().getMacKey()); + } catch (InvalidMacException e) { + throw new InvalidMessageException(e); + } + } + +} diff --git a/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyBundleMessage.java b/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyBundleMessage.java index bce131e624..6f65d47ad5 100644 --- a/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyBundleMessage.java +++ b/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyBundleMessage.java @@ -18,8 +18,8 @@ package org.whispersystems.textsecure.crypto.protocol; import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.InvalidKeyException; +import org.whispersystems.textsecure.crypto.InvalidMessageException; import org.whispersystems.textsecure.crypto.InvalidVersionException; -import org.whispersystems.textsecure.crypto.MessageCipher; import org.whispersystems.textsecure.crypto.PublicKey; import org.whispersystems.textsecure.util.Conversions; @@ -32,63 +32,52 @@ import org.whispersystems.textsecure.util.Conversions; */ public class PreKeyBundleMessage { - private static final int VERSION_LENGTH = MessageCipher.VERSION_LENGTH; - private static final int IDENTITY_KEY_LENGTH = IdentityKey.SIZE; - public static final int HEADER_LENGTH = IDENTITY_KEY_LENGTH + MessageCipher.HEADER_LENGTH; + public static final int SUPPORTED_VERSION = CiphertextMessage.SUPPORTED_VERSION; - private static final int VERSION_OFFSET = MessageCipher.VERSION_OFFSET; - private static final int IDENTITY_KEY_OFFSET = VERSION_OFFSET + MessageCipher.VERSION_LENGTH; - private static final int PUBLIC_KEY_OFFSET = IDENTITY_KEY_LENGTH + MessageCipher.NEXT_KEY_OFFSET; - private static final int PREKEY_ID_OFFSET = IDENTITY_KEY_LENGTH + MessageCipher.RECEIVER_KEY_ID_OFFSET; + private static final int VERSION_LENGTH = CiphertextMessage.VERSION_LENGTH; + private static final int IDENTITY_KEY_LENGTH = IdentityKey.SIZE; - private final byte[] messageBytes; + private static final int VERSION_OFFSET = CiphertextMessage.VERSION_OFFSET; + private static final int IDENTITY_KEY_OFFSET = VERSION_OFFSET + VERSION_LENGTH; - private final int supportedVersion; - private final int messageVersion; - private final int preKeyId; - private final IdentityKey identityKey; - private final PublicKey publicKey; - private final byte[] bundledMessage; + private final byte[] messageBytes; + private final CiphertextMessage bundledMessage; + private final IdentityKey identityKey; public PreKeyBundleMessage(byte[] messageBytes) - throws InvalidKeyException, InvalidVersionException + throws InvalidVersionException, InvalidKeyException { - this.messageBytes = messageBytes; - this.messageVersion = Conversions.highBitsToInt(this.messageBytes[VERSION_OFFSET]); + try { + this.messageBytes = messageBytes; + int messageVersion = Conversions.highBitsToInt(this.messageBytes[VERSION_OFFSET]); - if (messageVersion > MessageCipher.SUPPORTED_VERSION) - throw new InvalidVersionException("Key exchange with version: " + messageVersion + - " but we only support: " + MessageCipher.SUPPORTED_VERSION); + if (messageVersion > CiphertextMessage.SUPPORTED_VERSION) + throw new InvalidVersionException("Key exchange with version: " + messageVersion); - this.supportedVersion = Conversions.lowBitsToInt(messageBytes[VERSION_OFFSET]); - this.publicKey = new PublicKey(messageBytes, PUBLIC_KEY_OFFSET); - this.identityKey = new IdentityKey(messageBytes, IDENTITY_KEY_OFFSET); - this.preKeyId = Conversions.byteArrayToMedium(messageBytes, PREKEY_ID_OFFSET); - this.bundledMessage = new byte[messageBytes.length - IDENTITY_KEY_LENGTH]; + this.identityKey = new IdentityKey(messageBytes, IDENTITY_KEY_OFFSET); + byte[] bundledMessageBytes = new byte[messageBytes.length - IDENTITY_KEY_LENGTH]; + bundledMessageBytes[VERSION_OFFSET] = this.messageBytes[VERSION_OFFSET]; + System.arraycopy(messageBytes, IDENTITY_KEY_OFFSET+IDENTITY_KEY_LENGTH, bundledMessageBytes, + VERSION_OFFSET+VERSION_LENGTH, bundledMessageBytes.length-VERSION_LENGTH); - this.bundledMessage[VERSION_OFFSET] = this.messageBytes[VERSION_OFFSET]; - System.arraycopy(messageBytes, IDENTITY_KEY_OFFSET+IDENTITY_KEY_LENGTH, bundledMessage, VERSION_OFFSET+VERSION_LENGTH, bundledMessage.length-VERSION_LENGTH); + this.bundledMessage = new CiphertextMessage(bundledMessageBytes); + } catch (InvalidMessageException e) { + throw new InvalidKeyException(e); + } } - public PreKeyBundleMessage(IdentityKey identityKey, byte[] bundledMessage) { - try { - this.supportedVersion = MessageCipher.SUPPORTED_VERSION; - this.messageVersion = MessageCipher.SUPPORTED_VERSION; - this.identityKey = identityKey; - this.publicKey = new PublicKey(bundledMessage, MessageCipher.NEXT_KEY_OFFSET); - this.preKeyId = Conversions.byteArrayToMedium(bundledMessage, MessageCipher.RECEIVER_KEY_ID_OFFSET); - this.bundledMessage = bundledMessage; - this.messageBytes = new byte[IDENTITY_KEY_LENGTH + bundledMessage.length]; - - byte[] identityKeyBytes = identityKey.serialize(); - - messageBytes[VERSION_OFFSET] = bundledMessage[VERSION_OFFSET]; - System.arraycopy(identityKeyBytes, 0, messageBytes, IDENTITY_KEY_OFFSET, identityKeyBytes.length); - System.arraycopy(bundledMessage, VERSION_OFFSET+VERSION_LENGTH, messageBytes, IDENTITY_KEY_OFFSET+IDENTITY_KEY_LENGTH, bundledMessage.length-VERSION_LENGTH); - } catch (InvalidKeyException e) { - throw new AssertionError(e); - } + public PreKeyBundleMessage(CiphertextMessage bundledMessage, IdentityKey identityKey) { + this.bundledMessage = bundledMessage; + this.identityKey = identityKey; + this.messageBytes = new byte[IDENTITY_KEY_LENGTH + bundledMessage.serialize().length]; + + byte[] bundledMessageBytes = bundledMessage.serialize(); + byte[] identityKeyBytes = identityKey.serialize(); + + messageBytes[VERSION_OFFSET] = bundledMessageBytes[VERSION_OFFSET]; + System.arraycopy(identityKeyBytes, 0, messageBytes, IDENTITY_KEY_OFFSET, identityKeyBytes.length); + System.arraycopy(bundledMessageBytes, VERSION_OFFSET+VERSION_LENGTH, messageBytes, IDENTITY_KEY_OFFSET+IDENTITY_KEY_LENGTH, bundledMessageBytes.length-VERSION_LENGTH); } public byte[] serialize() { @@ -96,26 +85,22 @@ public class PreKeyBundleMessage { } public int getSupportedVersion() { - return supportedVersion; - } - - public int getMessageVersion() { - return messageVersion; + return bundledMessage.getSupportedVersion(); } public IdentityKey getIdentityKey() { return identityKey; } - public PublicKey getPublicKey() { - return publicKey; + public PublicKey getPublicKey() throws InvalidKeyException { + return new PublicKey(bundledMessage.getNextKeyBytes()); } - public byte[] getBundledMessage() { + public CiphertextMessage getBundledMessage() { return bundledMessage; } public int getPreKeyId() { - return preKeyId; + return bundledMessage.getReceiverKeyId(); } } diff --git a/library/src/org/whispersystems/textsecure/push/PushBody.java b/library/src/org/whispersystems/textsecure/push/PushBody.java new file mode 100644 index 0000000000..4e920c5413 --- /dev/null +++ b/library/src/org/whispersystems/textsecure/push/PushBody.java @@ -0,0 +1,20 @@ +package org.whispersystems.textsecure.push; + +public class PushBody { + + private final int type; + private final byte[] body; + + public PushBody(int type, byte[] body) { + this.type = type; + this.body = body; + } + + public int getType() { + return type; + } + + public byte[] getBody() { + return body; + } +} diff --git a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java index 8a5b692f0a..288e0649d3 100644 --- a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java +++ b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java @@ -84,32 +84,31 @@ public class PushServiceSocket { makeRequest(REGISTER_GCM_PATH, "DELETE", null); } - public void sendMessage(PushDestination recipient, byte[] body, int type) + public void sendMessage(PushDestination recipient, PushBody pushBody) throws IOException { OutgoingPushMessage message = new OutgoingPushMessage(recipient.getRelay(), recipient.getNumber(), - body, type); + pushBody.getBody(), + pushBody.getType()); sendMessage(new OutgoingPushMessageList(message)); } - public void sendMessage(List recipients, - List bodies, List types) + public void sendMessage(List recipients, List bodies) throws IOException { List messages = new LinkedList(); Iterator recipientsIterator = recipients.iterator(); - Iterator bodiesIterator = bodies.iterator(); - Iterator typesIterator = types.iterator(); + Iterator bodiesIterator = bodies.iterator(); while (recipientsIterator.hasNext()) { PushDestination recipient = recipientsIterator.next(); - byte[] body = bodiesIterator.next(); - int type = typesIterator.next(); + PushBody body = bodiesIterator.next(); - messages.add(new OutgoingPushMessage(recipient.getRelay(), recipient.getNumber(), body, type)); + messages.add(new OutgoingPushMessage(recipient.getRelay(), recipient.getNumber(), + body.getBody(), body.getType())); } sendMessage(new OutgoingPushMessageList(messages)); diff --git a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java index b4373011d4..7ce6cb7bd2 100644 --- a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java +++ b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java @@ -40,6 +40,7 @@ import org.whispersystems.textsecure.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.MemoryCleaner; +import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; import org.whispersystems.textsecure.storage.InvalidKeyIdException; @@ -182,9 +183,9 @@ public class ReceiveKeyActivity extends Activity { } else if (keyExchangeMessageBundle != null) { try { keyExchangeProcessor.processKeyExchangeMessage(keyExchangeMessageBundle); - byte[] bundledMessage = keyExchangeMessageBundle.getBundledMessage(); + CiphertextMessage bundledMessage = keyExchangeMessageBundle.getBundledMessage(); SmsTransportDetails transportDetails = new SmsTransportDetails(); - String messageBody = new String(transportDetails.getEncodedMessage(bundledMessage)); + String messageBody = new String(transportDetails.getEncodedMessage(bundledMessage.serialize())); DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this) .updateBundleMessageBody(masterSecret, messageId, messageBody); @@ -196,6 +197,10 @@ public class ReceiveKeyActivity extends Activity { Log.w("ReceiveKeyActivity", e); DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this) .markAsCorruptKeyExchange(messageId); + } catch (InvalidKeyException e) { + Log.w("ReceiveKeyActivity", e); + DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this) + .markAsCorruptKeyExchange(messageId); } } diff --git a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java index 6f0ea2269f..d9ded58c2a 100644 --- a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java +++ b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java @@ -48,7 +48,6 @@ import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MessageCipher; import org.whispersystems.textsecure.crypto.SessionCipher; import org.whispersystems.textsecure.push.IncomingPushMessage; -import org.whispersystems.textsecure.push.PushTransportDetails; import org.whispersystems.textsecure.util.Hex; import java.io.IOException; @@ -197,9 +196,9 @@ public class DecryptingQueue { } IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); - MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey, new PushTransportDetails()); + MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey); + byte[] plaintextBody = messageCipher.decrypt(recipient, message.getBody()); - byte[] plaintextBody = messageCipher.decrypt(recipient, message.getBody()); message = message.withBody(plaintextBody); sendResult(PushReceiver.RESULT_OK); } catch (InvalidMessageException e) { @@ -276,11 +275,13 @@ public class DecryptingQueue { synchronized (SessionCipher.CIPHER_LOCK) { Log.w("DecryptingQueue", "Decrypting: " + Hex.toString(ciphertextPduBytes)); - IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); - MessageCipher message = new MessageCipher(context, masterSecret, identityKey, new TextTransport()); + TextTransport transportDetails = new TextTransport(); + IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); + MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey); + byte[] ciphertext = transportDetails.getDecodedMessage(ciphertextPduBytes); try { - plaintextPduBytes = message.decrypt(recipient, ciphertextPduBytes); + plaintextPduBytes = messageCipher.decrypt(recipient, ciphertext); } catch (InvalidMessageException ime) { // XXX - For some reason, Sprint seems to append a single character to the // end of message text segments. I don't know why, so here we just try @@ -289,7 +290,8 @@ public class DecryptingQueue { Log.w("DecryptingQueue", "Attempting truncated decrypt..."); byte[] truncated = new byte[ciphertextPduBytes.length - 1]; System.arraycopy(ciphertextPduBytes, 0, truncated, 0, truncated.length); - plaintextPduBytes = message.decrypt(recipient, truncated); + ciphertext = transportDetails.getDecodedMessage(truncated); + plaintextPduBytes = messageCipher.decrypt(recipient, ciphertext); } else { throw ime; } @@ -311,6 +313,9 @@ public class DecryptingQueue { } catch (MmsException mme) { Log.w("DecryptingQueue", mme); database.markAsDecryptFailed(messageId, threadId); + } catch (IOException e) { + Log.w("DecryptingQueue", e); + database.markAsDecryptFailed(messageId, threadId); } } } @@ -354,10 +359,13 @@ public class DecryptingQueue { return; } - IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); - MessageCipher message = new MessageCipher(context, masterSecret, identityKey, new SmsTransportDetails()); + SmsTransportDetails transportDetails = new SmsTransportDetails(); + IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); + MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey); + byte[] ciphertext = transportDetails.getDecodedMessage(body.getBytes()); + byte[] paddedPlaintext = messageCipher.decrypt(recipient, ciphertext); - plaintextBody = new String(message.decrypt(recipient, body.getBytes())); + plaintextBody = new String(transportDetails.getStrippedPaddingMessageBody(paddedPlaintext)); } catch (InvalidMessageException e) { Log.w("DecryptionQueue", e); database.markAsDecryptFailed(messageId); @@ -366,6 +374,10 @@ public class DecryptingQueue { Log.w("DecryptionQueue", e); database.markAsDecryptFailed(messageId); return; + } catch (IOException e) { + Log.w("DecryptionQueue", e); + database.markAsDecryptFailed(messageId); + return; } } diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java index 781041f116..360ca80b6a 100644 --- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java +++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java @@ -22,12 +22,17 @@ import android.util.Log; import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage; import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.service.KeyCachingService; +import org.thoughtcrime.securesms.sms.MessageSender; +import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage; import org.whispersystems.textsecure.crypto.IdentityKey; +import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.KeyPair; import org.whispersystems.textsecure.crypto.KeyUtil; import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.MessageCipher; import org.whispersystems.textsecure.crypto.PublicKey; +import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; import org.whispersystems.textsecure.push.PreKeyEntity; import org.whispersystems.textsecure.storage.InvalidKeyIdException; @@ -35,10 +40,6 @@ import org.whispersystems.textsecure.storage.LocalKeyRecord; import org.whispersystems.textsecure.storage.PreKeyRecord; import org.whispersystems.textsecure.storage.RemoteKeyRecord; import org.whispersystems.textsecure.storage.SessionRecord; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.service.KeyCachingService; -import org.thoughtcrime.securesms.sms.MessageSender; -import org.thoughtcrime.securesms.sms.OutgoingKeyExchangeMessage; import org.whispersystems.textsecure.util.Conversions; import org.whispersystems.textsecure.util.Medium; @@ -103,7 +104,9 @@ public class KeyExchangeProcessor { (localKeyRecord.getCurrentKeyPair() != null && localKeyRecord.getCurrentKeyPair().getId() != responseKeyId); } - public void processKeyExchangeMessage(PreKeyBundleMessage message) throws InvalidKeyIdException { + public void processKeyExchangeMessage(PreKeyBundleMessage message) + throws InvalidKeyIdException, InvalidKeyException + { int preKeyId = message.getPreKeyId(); PublicKey remoteKey = message.getPublicKey(); IdentityKey remoteIdentity = message.getIdentityKey(); @@ -131,7 +134,7 @@ public class KeyExchangeProcessor { sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(), remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes()); sessionRecord.setIdentityKey(remoteIdentity); - sessionRecord.setSessionVersion(Math.min(message.getSupportedVersion(), MessageCipher.SUPPORTED_VERSION)); + sessionRecord.setSessionVersion(Math.min(message.getSupportedVersion(), PreKeyBundleMessage.SUPPORTED_VERSION)); sessionRecord.setNegotiatedSessionVersion(sessionRecord.getSessionVersion()); localKeyRecord.save(); @@ -159,8 +162,8 @@ public class KeyExchangeProcessor { sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(), remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes()); sessionRecord.setIdentityKey(message.getIdentityKey()); - sessionRecord.setNegotiatedSessionVersion(MessageCipher.SUPPORTED_VERSION); - sessionRecord.setSessionVersion(MessageCipher.SUPPORTED_VERSION); + sessionRecord.setNegotiatedSessionVersion(CiphertextMessage.SUPPORTED_VERSION); + sessionRecord.setSessionVersion(CiphertextMessage.SUPPORTED_VERSION); sessionRecord.setPrekeyBundleRequired(true); sessionRecord.save(); @@ -174,7 +177,7 @@ public class KeyExchangeProcessor { if (needsResponseFromUs()) { localKeyRecord = KeyUtil.initializeRecordFor(recipient, context, masterSecret); - KeyExchangeMessage ourMessage = new KeyExchangeMessage(context, masterSecret, Math.min(MessageCipher.SUPPORTED_VERSION, message.getMaxVersion()), localKeyRecord, initiateKeyId); + KeyExchangeMessage ourMessage = new KeyExchangeMessage(context, masterSecret, Math.min(CiphertextMessage.SUPPORTED_VERSION, message.getMaxVersion()), localKeyRecord, initiateKeyId); OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, ourMessage.serialize()); Log.w("KeyExchangeProcessor", "Responding with key exchange message fingerprint: " + ourMessage.getPublicKey().getFingerprint()); Log.w("KeyExchangeProcessor", "Which has a local key record fingerprint: " + localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprint()); @@ -188,10 +191,10 @@ public class KeyExchangeProcessor { sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(), remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes()); sessionRecord.setIdentityKey(message.getIdentityKey()); - sessionRecord.setSessionVersion(Math.min(MessageCipher.SUPPORTED_VERSION, message.getMaxVersion())); + sessionRecord.setSessionVersion(Math.min(CiphertextMessage.SUPPORTED_VERSION, message.getMaxVersion())); sessionRecord.setNegotiatedSessionVersion(sessionRecord.getSessionVersion()); - Log.w("KeyExchangeUtil", "Setting session version: " + Math.min(MessageCipher.SUPPORTED_VERSION, message.getMaxVersion())); + Log.w("KeyExchangeUtil", "Setting session version: " + Math.min(CiphertextMessage.SUPPORTED_VERSION, message.getMaxVersion())); sessionRecord.save(); diff --git a/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java b/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java index 11aa016393..f5e81c0f12 100644 --- a/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java +++ b/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java @@ -20,12 +20,12 @@ import android.content.Context; import android.util.Log; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; -import org.whispersystems.textsecure.crypto.InvalidVersionException; import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.InvalidKeyException; +import org.whispersystems.textsecure.crypto.InvalidVersionException; import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.MessageCipher; import org.whispersystems.textsecure.crypto.PublicKey; +import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.storage.LocalKeyRecord; import org.whispersystems.textsecure.util.Base64; import org.whispersystems.textsecure.util.Conversions; @@ -59,6 +59,8 @@ import java.io.IOException; public class KeyExchangeMessage { + private static final int SUPPORTED_VERSION = CiphertextMessage.SUPPORTED_VERSION; + private final int messageVersion; private final int supportedVersion; private final PublicKey publicKey; @@ -68,7 +70,7 @@ public class KeyExchangeMessage { public KeyExchangeMessage(Context context, MasterSecret masterSecret, int messageVersion, LocalKeyRecord record, int highIdBits) { this.publicKey = new PublicKey(record.getCurrentKeyPair().getPublicKey()); this.messageVersion = messageVersion; - this.supportedVersion = MessageCipher.SUPPORTED_VERSION; + this.supportedVersion = SUPPORTED_VERSION; publicKey.setId(publicKey.getId() | (highIdBits << 12)); @@ -100,9 +102,9 @@ public class KeyExchangeMessage { this.supportedVersion = Conversions.lowBitsToInt(keyBytes[0]); this.serialized = messageBody; - if (messageVersion > MessageCipher.SUPPORTED_VERSION) + if (messageVersion > SUPPORTED_VERSION) throw new InvalidVersionException("Key exchange with version: " + messageVersion + - " but we only support: " + MessageCipher.SUPPORTED_VERSION); + " but we only support: " + SUPPORTED_VERSION); if (messageVersion >= 1) keyBytes = Base64.decodeWithoutPadding(messageBody); diff --git a/src/org/thoughtcrime/securesms/service/PushReceiver.java b/src/org/thoughtcrime/securesms/service/PushReceiver.java index ead7b8dab4..5f3b0d95b3 100644 --- a/src/org/thoughtcrime/securesms/service/PushReceiver.java +++ b/src/org/thoughtcrime/securesms/service/PushReceiver.java @@ -6,6 +6,7 @@ import android.util.Log; import android.util.Pair; import com.google.protobuf.InvalidProtocolBufferException; + import org.thoughtcrime.securesms.crypto.DecryptingQueue; import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; import org.thoughtcrime.securesms.database.DatabaseFactory; @@ -21,7 +22,6 @@ import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage; import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.SmsTransportDetails; -import org.thoughtcrime.securesms.transport.SmsTransport; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidVersionException; @@ -99,7 +99,7 @@ public class PushReceiver { if (processor.isTrusted(preKeyExchange)) { processor.processKeyExchangeMessage(preKeyExchange); - IncomingPushMessage bundledMessage = message.withBody(preKeyExchange.getBundledMessage()); + IncomingPushMessage bundledMessage = message.withBody(preKeyExchange.getBundledMessage().serialize()); handleReceivedSecureMessage(masterSecret, bundledMessage); } else { SmsTransportDetails transportDetails = new SmsTransportDetails(); diff --git a/src/org/thoughtcrime/securesms/service/SmsReceiver.java b/src/org/thoughtcrime/securesms/service/SmsReceiver.java index bf0b205c82..bce7133dec 100644 --- a/src/org/thoughtcrime/securesms/service/SmsReceiver.java +++ b/src/org/thoughtcrime/securesms/service/SmsReceiver.java @@ -40,6 +40,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidVersionException; import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; import org.whispersystems.textsecure.storage.InvalidKeyIdException; @@ -110,7 +111,8 @@ public class SmsReceiver { if (processor.isTrusted(preKeyExchange)) { processor.processKeyExchangeMessage(preKeyExchange); - String bundledMessageBody = new String(transportDetails.getEncodedMessage(preKeyExchange.getBundledMessage())); + CiphertextMessage ciphertextMessage = preKeyExchange.getBundledMessage(); + String bundledMessageBody = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize())); IncomingEncryptedMessage bundledMessage = new IncomingEncryptedMessage(message, bundledMessageBody); Pair messageAndThreadId = storeSecureMessage(masterSecret, bundledMessage); diff --git a/src/org/thoughtcrime/securesms/sms/SmsTransportDetails.java b/src/org/thoughtcrime/securesms/sms/SmsTransportDetails.java index 9a835994d6..150b164d95 100644 --- a/src/org/thoughtcrime/securesms/sms/SmsTransportDetails.java +++ b/src/org/thoughtcrime/securesms/sms/SmsTransportDetails.java @@ -18,9 +18,9 @@ package org.thoughtcrime.securesms.sms; import android.util.Log; -import org.whispersystems.textsecure.crypto.SessionCipher; -import org.whispersystems.textsecure.crypto.TransportDetails; import org.thoughtcrime.securesms.protocol.WirePrefix; +import org.whispersystems.textsecure.crypto.TransportDetails; +import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.util.Base64; import java.io.IOException; @@ -35,7 +35,7 @@ public class SmsTransportDetails implements TransportDetails { public static final int MULTI_MESSAGE_MAX_BYTES = BASE_MAX_BYTES - MultipartSmsTransportMessage.MULTI_MESSAGE_MULTIPART_OVERHEAD; public static final int FIRST_MULTI_MESSAGE_MAX_BYTES = BASE_MAX_BYTES - MultipartSmsTransportMessage.FIRST_MULTI_MESSAGE_MULTIPART_OVERHEAD; - public static final int ENCRYPTED_SINGLE_MESSAGE_BODY_MAX_SIZE = SINGLE_MESSAGE_MAX_BYTES - SessionCipher.ENCRYPTED_MESSAGE_OVERHEAD; + public static final int ENCRYPTED_SINGLE_MESSAGE_BODY_MAX_SIZE = SINGLE_MESSAGE_MAX_BYTES - CiphertextMessage.ENCRYPTED_MESSAGE_OVERHEAD; @Override public byte[] getEncodedMessage(byte[] messageWithMac) { @@ -73,7 +73,7 @@ public class SmsTransportDetails implements TransportDetails { @Override public byte[] getPaddedMessageBody(byte[] messageBody) { int paddedBodySize = getMaxBodySizeForBytes(messageBody.length); - Log.w("SessionCipher", "Padding message body out to: " + paddedBodySize); + Log.w("SmsTransportDetails", "Padding message body out to: " + paddedBodySize); byte[] paddedBody = new byte[paddedBodySize]; System.arraycopy(messageBody, 0, paddedBody, 0, messageBody.length); @@ -82,7 +82,7 @@ public class SmsTransportDetails implements TransportDetails { } private int getMaxBodySizeForBytes(int bodyLength) { - int encryptedBodyLength = bodyLength + SessionCipher.ENCRYPTED_MESSAGE_OVERHEAD; + int encryptedBodyLength = bodyLength + CiphertextMessage.ENCRYPTED_MESSAGE_OVERHEAD; int messageRecordsForBody = getMessageCountForBytes(encryptedBodyLength); if (messageRecordsForBody == 1) { @@ -91,7 +91,7 @@ public class SmsTransportDetails implements TransportDetails { return FIRST_MULTI_MESSAGE_MAX_BYTES + (MULTI_MESSAGE_MAX_BYTES * (messageRecordsForBody-1)) - - SessionCipher.ENCRYPTED_MESSAGE_OVERHEAD; + CiphertextMessage.ENCRYPTED_MESSAGE_OVERHEAD; } } diff --git a/src/org/thoughtcrime/securesms/transport/MmsTransport.java b/src/org/thoughtcrime/securesms/transport/MmsTransport.java index 5c7d7e222f..033c9a6960 100644 --- a/src/org/thoughtcrime/securesms/transport/MmsTransport.java +++ b/src/org/thoughtcrime/securesms/transport/MmsTransport.java @@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.mms.MmsSendHelper; import org.thoughtcrime.securesms.mms.TextTransport; import org.thoughtcrime.securesms.protocol.WirePrefix; import org.thoughtcrime.securesms.recipients.Recipient; +import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.util.Hex; import java.io.IOException; @@ -134,10 +135,14 @@ public class MmsTransport { return encryptedPdu; } - private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipient, byte[] pduBytes) { - IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); - MessageCipher message = new MessageCipher(context, masterSecret, identityKey, new TextTransport()); - return message.encrypt(new Recipient(null, recipient, null, null), pduBytes); + private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipientString, byte[] pduBytes) { + TextTransport transportDetails = new TextTransport(); + Recipient recipient = new Recipient(null, recipientString, null, null); + IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); + MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey); + CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, pduBytes); + + return transportDetails.getEncodedMessage(ciphertextMessage.serialize()); } private boolean isInconsistentResponse(SendReq message, SendConf response) { diff --git a/src/org/thoughtcrime/securesms/transport/PushTransport.java b/src/org/thoughtcrime/securesms/transport/PushTransport.java index a5c542e130..7f9351c39d 100644 --- a/src/org/thoughtcrime/securesms/transport/PushTransport.java +++ b/src/org/thoughtcrime/securesms/transport/PushTransport.java @@ -2,9 +2,9 @@ package org.thoughtcrime.securesms.transport; import android.content.Context; import android.util.Log; -import android.util.Pair; import com.google.protobuf.ByteString; + import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; @@ -13,7 +13,6 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.Recipients; -import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePushCredentials; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.textsecure.crypto.AttachmentCipher; @@ -22,19 +21,17 @@ import org.whispersystems.textsecure.crypto.IdentityKeyPair; import org.whispersystems.textsecure.crypto.KeyUtil; import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MessageCipher; +import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; -import org.whispersystems.textsecure.directory.Directory; import org.whispersystems.textsecure.push.OutgoingPushMessage; import org.whispersystems.textsecure.push.PreKeyEntity; import org.whispersystems.textsecure.push.PushAttachmentData; import org.whispersystems.textsecure.push.PushAttachmentPointer; +import org.whispersystems.textsecure.push.PushBody; import org.whispersystems.textsecure.push.PushDestination; import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent; import org.whispersystems.textsecure.push.PushServiceSocket; -import org.whispersystems.textsecure.push.PushTransportDetails; import org.whispersystems.textsecure.push.RateLimitException; -import org.whispersystems.textsecure.push.RawTransportDetails; -import org.whispersystems.textsecure.util.PhoneNumberFormatter; import java.io.IOException; import java.util.LinkedList; @@ -62,14 +59,11 @@ public class PushTransport extends BaseTransport { PushDestination destination = PushDestination.create(context, credentials, recipient.getNumber()); - String plaintextBody = message.getBody().getBody(); - byte[] plaintext = PushMessageContent.newBuilder() - .setBody(plaintextBody) - .build().toByteArray(); - - Pair typeAndCiphertext = getEncryptedMessage(socket, recipient, destination, plaintext); + String plaintextBody = message.getBody().getBody(); + byte[] plaintext = PushMessageContent.newBuilder().setBody(plaintextBody).build().toByteArray(); + PushBody pushBody = getEncryptedMessage(socket, recipient, destination, plaintext); - socket.sendMessage(destination, typeAndCiphertext.second, typeAndCiphertext.first); + socket.sendMessage(destination, pushBody); context.sendBroadcast(constructSentIntent(context, message.getId(), message.getType())); } catch (RateLimitException e) { @@ -83,9 +77,7 @@ public class PushTransport extends BaseTransport { TextSecurePushCredentials credentials = TextSecurePushCredentials.getInstance(); PushServiceSocket socket = new PushServiceSocket(context, credentials); String messageBody = PartParser.getMessageText(message.getBody()); - - List ciphertext = new LinkedList(); - List types = new LinkedList(); + List pushBodies = new LinkedList(); for (PushDestination destination : destinations) { Recipients recipients = RecipientFactory.getRecipientsFromString(context, destination.getNumber(), false); @@ -107,15 +99,13 @@ public class PushTransport extends BaseTransport { builder.addAttachments(attachmentBuilder.build()); } - byte[] plaintext = builder.build().toByteArray(); - Pair typeAndCiphertext = getEncryptedMessage(socket, recipients.getPrimaryRecipient(), - destination, plaintext); + byte[] plaintext = builder.build().toByteArray(); + PushBody pushBody = getEncryptedMessage(socket, recipients.getPrimaryRecipient(), destination, plaintext); - types.add(typeAndCiphertext.first); - ciphertext.add(typeAndCiphertext.second); + pushBodies.add(pushBody); } - socket.sendMessage(destinations, ciphertext, types); + socket.sendMessage(destinations, pushBodies); } catch (RateLimitException e) { Log.w("PushTransport", e); @@ -150,37 +140,34 @@ public class PushTransport extends BaseTransport { return attachments; } - private Pair getEncryptedMessage(PushServiceSocket socket, - Recipient recipient, - PushDestination pushDestination, - byte[] plaintext) + private PushBody getEncryptedMessage(PushServiceSocket socket, Recipient recipient, + PushDestination pushDestination, byte[] plaintext) throws IOException { if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) { Log.w("PushTransport", "Sending standard ciphertext message..."); byte[] ciphertext = getEncryptedMessageForExistingSession(recipient, plaintext); - return new Pair(OutgoingPushMessage.TYPE_MESSAGE_CIPHERTEXT, ciphertext); + return new PushBody(OutgoingPushMessage.TYPE_MESSAGE_CIPHERTEXT, ciphertext); } else if (KeyUtil.isSessionFor(context, recipient)) { Log.w("PushTransport", "Sending prekeybundle ciphertext message for existing session..."); byte[] ciphertext = getEncryptedPrekeyBundleMessageForExistingSession(recipient, plaintext); - return new Pair(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext); + return new PushBody(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext); } else { Log.w("PushTransport", "Sending prekeybundle ciphertext message for new session..."); byte[] ciphertext = getEncryptedPrekeyBundleMessageForNewSession(socket, recipient, pushDestination, plaintext); - return new Pair(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext); + return new PushBody(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext); } } private byte[] getEncryptedPrekeyBundleMessageForExistingSession(Recipient recipient, byte[] plaintext) { - IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); - IdentityKey identityKey = identityKeyPair.getPublicKey(); - - MessageCipher message = new MessageCipher(context, masterSecret, identityKeyPair, new RawTransportDetails()); - byte[] bundledMessage = message.encrypt(recipient, plaintext); + IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); + IdentityKey identityKey = identityKeyPair.getPublicKey(); + MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair); + CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, plaintext); + PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(ciphertextMessage, identityKey); - PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(identityKey, bundledMessage); return preKeyBundleMessage.serialize(); } @@ -197,21 +184,21 @@ public class PushTransport extends BaseTransport { processor.processKeyExchangeMessage(preKey); - MessageCipher message = new MessageCipher(context, masterSecret, identityKeyPair, new RawTransportDetails()); - byte[] bundledMessage = message.encrypt(recipient, plaintext); + MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair); + CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, plaintext); + PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(ciphertextMessage, identityKey); - PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(identityKey, bundledMessage); return preKeyBundleMessage.serialize(); } private byte[] getEncryptedMessageForExistingSession(Recipient recipient, byte[] plaintext) throws IOException { - IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); - MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair, - new PushTransportDetails()); + IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); + MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair); + CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, plaintext); - return messageCipher.encrypt(recipient, plaintext); + return ciphertextMessage.serialize(); } } diff --git a/src/org/thoughtcrime/securesms/transport/SmsTransport.java b/src/org/thoughtcrime/securesms/transport/SmsTransport.java index b4b709e677..fc154c3039 100644 --- a/src/org/thoughtcrime/securesms/transport/SmsTransport.java +++ b/src/org/thoughtcrime/securesms/transport/SmsTransport.java @@ -6,21 +6,21 @@ import android.telephony.SmsManager; import android.util.Log; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; -import org.thoughtcrime.securesms.sms.OutgoingPrekeyBundleMessage;import org.whispersystems.textsecure.push.RawTransportDetails; -import org.whispersystems.textsecure.crypto.IdentityKeyPair; -import org.whispersystems.textsecure.crypto.KeyUtil; -import org.whispersystems.textsecure.crypto.MasterSecret; -import org.whispersystems.textsecure.crypto.MessageCipher; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.service.SendReceiveService; import org.thoughtcrime.securesms.service.SmsDeliveryListener; import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler; +import org.thoughtcrime.securesms.sms.OutgoingPrekeyBundleMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.sms.SmsTransportDetails; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.textsecure.crypto.IdentityKeyPair; +import org.whispersystems.textsecure.crypto.KeyUtil; +import org.whispersystems.textsecure.crypto.MasterSecret; +import org.whispersystems.textsecure.crypto.MessageCipher; +import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage; import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; -import org.whispersystems.textsecure.util.Base64; import java.util.ArrayList; @@ -144,23 +144,28 @@ public class SmsTransport extends BaseTransport { private OutgoingTextMessage getAsymmetricEncrypt(MasterSecret masterSecret, OutgoingTextMessage message) { - Recipient recipient = message.getRecipients().getPrimaryRecipient(); - String body = message.getMessageBody(); - IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); + Recipient recipient = message.getRecipients().getPrimaryRecipient(); + String body = message.getMessageBody(); + IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); + SmsTransportDetails transportDetails = new SmsTransportDetails(); if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) { Log.w("SmsTransport", "Delivering standard ciphertext..."); - MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey, new SmsTransportDetails()); - byte[] ciphertext = messageCipher.encrypt(recipient, body.getBytes()); - return message.withBody(new String(ciphertext)); + MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey); + byte[] paddedPlaintext = transportDetails.getPaddedMessageBody(body.getBytes()); + CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, paddedPlaintext); + String ciphertxt = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize())); + + return message.withBody(ciphertxt); } else { Log.w("SmsTransport", "Delivering prekeybundle ciphertext..."); - MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey, new RawTransportDetails()); - byte[] bundledMessage = messageCipher.encrypt(recipient, body.getBytes()); - PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(identityKey.getPublicKey(), bundledMessage); + MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey); + CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, body.getBytes()); + PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(ciphertextMessage, identityKey.getPublicKey()); + byte[] cipherText = preKeyBundleMessage.serialize(); - return new OutgoingPrekeyBundleMessage(message, Base64.encodeBytesWithoutPadding(preKeyBundleMessage.serialize())); + return new OutgoingPrekeyBundleMessage(message, new String(transportDetails.getEncodedMessage(cipherText))); } } }