Refactor the ciphertext message parsing and building.

This commit is contained in:
Moxie Marlinspike 2013-10-31 17:23:45 -07:00
parent 6e640db39c
commit dbc070cd65
17 changed files with 363 additions and 316 deletions

View File

@ -19,11 +19,8 @@ package org.whispersystems.textsecure.crypto;
import android.content.Context; import android.content.Context;
import org.whispersystems.textsecure.crypto.SessionCipher.SessionCipherContext; import org.whispersystems.textsecure.crypto.SessionCipher.SessionCipherContext;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.storage.CanonicalRecipientAddress; 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. * Parses and serializes the encrypted message format.
@ -33,48 +30,23 @@ import java.nio.ByteBuffer;
public class MessageCipher { 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 Context context;
private final MasterSecret masterSecret; private final MasterSecret masterSecret;
private final IdentityKeyPair localIdentityKey; private final IdentityKeyPair localIdentityKey;
private final TransportDetails transportDetails;
public MessageCipher(Context context, MasterSecret masterSecret, public MessageCipher(Context context, MasterSecret masterSecret, IdentityKeyPair localIdentityKey) {
IdentityKeyPair localIdentityKey,
TransportDetails transportDetails)
{
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
this.masterSecret = masterSecret; this.masterSecret = masterSecret;
this.localIdentityKey = localIdentityKey; this.localIdentityKey = localIdentityKey;
this.transportDetails = transportDetails;
} }
public byte[] encrypt(CanonicalRecipientAddress recipient, byte[] plaintext) { public CiphertextMessage encrypt(CanonicalRecipientAddress recipient, byte[] paddedBody) {
synchronized (SessionCipher.CIPHER_LOCK) { synchronized (SessionCipher.CIPHER_LOCK) {
byte[] paddedBody = transportDetails.getPaddedMessageBody(plaintext); SessionCipher sessionCipher = new SessionCipher();
SessionCipher sessionCipher = new SessionCipher(); SessionCipherContext sessionContext = sessionCipher.getEncryptionContext(context, masterSecret, localIdentityKey, recipient);
SessionCipherContext sessionContext = sessionCipher.getEncryptionContext(context, masterSecret, localIdentityKey, recipient); byte[] ciphertextBody = sessionCipher.encrypt(sessionContext, paddedBody);
byte[] ciphertextBody = sessionCipher.encrypt(sessionContext, paddedBody);
byte[] formattedCiphertext = getFormattedCiphertext(sessionContext, ciphertextBody);
byte[] ciphertextMessage = sessionCipher.mac(sessionContext, formattedCiphertext);
return transportDetails.getEncodedMessage(ciphertextMessage); return new CiphertextMessage(sessionContext, ciphertextBody);
} }
} }
@ -83,25 +55,17 @@ public class MessageCipher {
{ {
synchronized (SessionCipher.CIPHER_LOCK) { synchronized (SessionCipher.CIPHER_LOCK) {
try { try {
byte[] decodedMessage = transportDetails.getDecodedMessage(ciphertext); CiphertextMessage message = new CiphertextMessage(ciphertext);
if (decodedMessage.length <= HEADER_LENGTH) { int messageVersion = message.getCurrentVersion();
throw new InvalidMessageException("Message is shorter than headers"); 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 messageVersion = getMessageVersion(decodedMessage);
if (messageVersion > SUPPORTED_VERSION) {
throw new InvalidMessageException("Unsupported version: " + messageVersion);
}
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(); SessionCipher sessionCipher = new SessionCipher();
SessionCipherContext sessionContext = sessionCipher.getDecryptionContext(context, masterSecret, SessionCipherContext sessionContext = sessionCipher.getDecryptionContext(context, masterSecret,
localIdentityKey, localIdentityKey,
@ -112,67 +76,13 @@ public class MessageCipher {
messageVersion, messageVersion,
negotiatedVersion); negotiatedVersion);
sessionCipher.verifyMac(sessionContext, decodedMessage); message.verifyMac(sessionContext);
byte[] plaintextWithPadding = sessionCipher.decrypt(sessionContext, ciphertextBody); return sessionCipher.decrypt(sessionContext, body);
return transportDetails.getStrippedPaddingMessageBody(plaintextWithPadding);
} catch (IOException e) {
throw new InvalidMessageException(e);
} catch (InvalidKeyException e) { } catch (InvalidKeyException e) {
throw new InvalidMessageException(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);
}
} }

View File

@ -31,7 +31,7 @@ public class MessageMac {
public static final int MAC_LENGTH = 10; 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 { try {
Mac mac = Mac.getInstance("HmacSHA1"); Mac mac = Mac.getInstance("HmacSHA1");
mac.init(macKey); mac.init(macKey);
@ -51,35 +51,18 @@ public class MessageMac {
} }
} }
public static byte[] buildMessageWithMac(byte[] message, SecretKeySpec macKey) { public static void verifyMac(byte[] message, int offset, int length,
byte[] macBytes = calculateMac(message, 0, message.length, macKey); byte[] receivedMac, SecretKeySpec macKey)
byte[] combined = new byte[macBytes.length + message.length]; throws InvalidMacException
System.arraycopy(message, 0, combined, 0, message.length); {
System.arraycopy(macBytes, 0, combined, message.length, macBytes.length); byte[] localMac = calculateMac(message, offset, length, macKey);
return combined; Log.w("MessageMac", "Local Mac: " + Hex.toString(localMac));
} Log.w("MessageMac", "Remot Mac: " + Hex.toString(receivedMac));
public static byte[] getMessageWithoutMac(byte[] message) throws InvalidMacException { if (!Arrays.equals(localMac, receivedMac)) {
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));
if (!Arrays.equals(localMacBytes, receivedMacBytes))
throw new InvalidMacException("MAC on message does not match calculated MAC."); throw new InvalidMacException("MAC on message does not match calculated MAC.");
}
} }
} }

View File

@ -20,6 +20,7 @@ import android.content.Context;
import android.util.Log; import android.util.Log;
import org.spongycastle.crypto.params.ECPublicKeyParameters; import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.storage.CanonicalRecipientAddress; import org.whispersystems.textsecure.storage.CanonicalRecipientAddress;
import org.whispersystems.textsecure.storage.InvalidKeyIdException; import org.whispersystems.textsecure.storage.InvalidKeyIdException;
import org.whispersystems.textsecure.storage.LocalKeyRecord; import org.whispersystems.textsecure.storage.LocalKeyRecord;
@ -56,8 +57,6 @@ public class SessionCipher {
public static final int CIPHER_KEY_LENGTH = 16; public static final int CIPHER_KEY_LENGTH = 16;
public static final int MAC_KEY_LENGTH = 20; 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, public SessionCipherContext getEncryptionContext(Context context,
MasterSecret masterSecret, MasterSecret masterSecret,
IdentityKeyPair localIdentityKey, IdentityKeyPair localIdentityKey,
@ -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) { private SecretKeySpec deriveMacSecret(SecretKeySpec key) {
try { try {
MessageDigest md = MessageDigest.getInstance("SHA-1"); MessageDigest md = MessageDigest.getInstance("SHA-1");
@ -314,7 +299,7 @@ public class SessionCipher {
IdentityKey remoteIdentityKey = records.getSessionRecord().getIdentityKey(); IdentityKey remoteIdentityKey = records.getSessionRecord().getIdentityKey();
if (isInitiallyExchangedKeys(records, localKeyId, remoteKeyId) && if (isInitiallyExchangedKeys(records, localKeyId, remoteKeyId) &&
messageVersion >= MessageCipher.CRADLE_AGREEMENT_VERSION) messageVersion >= CiphertextMessage.CRADLE_AGREEMENT_VERSION)
{ {
return SharedSecretCalculator.calculateSharedSecret(localKeyPair, localIdentityKey, return SharedSecretCalculator.calculateSharedSecret(localKeyPair, localIdentityKey,
remoteKey, remoteIdentityKey); remoteKey, remoteIdentityKey);

View File

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

View File

@ -18,8 +18,8 @@ package org.whispersystems.textsecure.crypto.protocol;
import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.InvalidMessageException;
import org.whispersystems.textsecure.crypto.InvalidVersionException; import org.whispersystems.textsecure.crypto.InvalidVersionException;
import org.whispersystems.textsecure.crypto.MessageCipher;
import org.whispersystems.textsecure.crypto.PublicKey; import org.whispersystems.textsecure.crypto.PublicKey;
import org.whispersystems.textsecure.util.Conversions; import org.whispersystems.textsecure.util.Conversions;
@ -32,63 +32,52 @@ import org.whispersystems.textsecure.util.Conversions;
*/ */
public class PreKeyBundleMessage { public class PreKeyBundleMessage {
private static final int VERSION_LENGTH = MessageCipher.VERSION_LENGTH; public static final int SUPPORTED_VERSION = CiphertextMessage.SUPPORTED_VERSION;
private static final int VERSION_LENGTH = CiphertextMessage.VERSION_LENGTH;
private static final int IDENTITY_KEY_LENGTH = IdentityKey.SIZE; private static final int IDENTITY_KEY_LENGTH = IdentityKey.SIZE;
public static final int HEADER_LENGTH = IDENTITY_KEY_LENGTH + MessageCipher.HEADER_LENGTH;
private static final int VERSION_OFFSET = MessageCipher.VERSION_OFFSET; private static final int VERSION_OFFSET = CiphertextMessage.VERSION_OFFSET;
private static final int IDENTITY_KEY_OFFSET = VERSION_OFFSET + MessageCipher.VERSION_LENGTH; private static final int IDENTITY_KEY_OFFSET = VERSION_OFFSET + 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 final byte[] messageBytes; private final byte[] messageBytes;
private final CiphertextMessage bundledMessage;
private final int supportedVersion; private final IdentityKey identityKey;
private final int messageVersion;
private final int preKeyId;
private final IdentityKey identityKey;
private final PublicKey publicKey;
private final byte[] bundledMessage;
public PreKeyBundleMessage(byte[] messageBytes) public PreKeyBundleMessage(byte[] messageBytes)
throws InvalidKeyException, InvalidVersionException throws InvalidVersionException, InvalidKeyException
{ {
this.messageBytes = messageBytes; try {
this.messageVersion = Conversions.highBitsToInt(this.messageBytes[VERSION_OFFSET]); this.messageBytes = messageBytes;
int messageVersion = Conversions.highBitsToInt(this.messageBytes[VERSION_OFFSET]);
if (messageVersion > MessageCipher.SUPPORTED_VERSION) if (messageVersion > CiphertextMessage.SUPPORTED_VERSION)
throw new InvalidVersionException("Key exchange with version: " + messageVersion + throw new InvalidVersionException("Key exchange with version: " + messageVersion);
" but we only support: " + MessageCipher.SUPPORTED_VERSION);
this.supportedVersion = Conversions.lowBitsToInt(messageBytes[VERSION_OFFSET]); this.identityKey = new IdentityKey(messageBytes, IDENTITY_KEY_OFFSET);
this.publicKey = new PublicKey(messageBytes, PUBLIC_KEY_OFFSET); byte[] bundledMessageBytes = new byte[messageBytes.length - IDENTITY_KEY_LENGTH];
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];
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]; this.bundledMessage = new CiphertextMessage(bundledMessageBytes);
System.arraycopy(messageBytes, IDENTITY_KEY_OFFSET+IDENTITY_KEY_LENGTH, bundledMessage, VERSION_OFFSET+VERSION_LENGTH, bundledMessage.length-VERSION_LENGTH); } catch (InvalidMessageException e) {
throw new InvalidKeyException(e);
}
} }
public PreKeyBundleMessage(IdentityKey identityKey, byte[] bundledMessage) { public PreKeyBundleMessage(CiphertextMessage bundledMessage, IdentityKey identityKey) {
try { this.bundledMessage = bundledMessage;
this.supportedVersion = MessageCipher.SUPPORTED_VERSION; this.identityKey = identityKey;
this.messageVersion = MessageCipher.SUPPORTED_VERSION; this.messageBytes = new byte[IDENTITY_KEY_LENGTH + bundledMessage.serialize().length];
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(); byte[] bundledMessageBytes = bundledMessage.serialize();
byte[] identityKeyBytes = identityKey.serialize();
messageBytes[VERSION_OFFSET] = bundledMessage[VERSION_OFFSET]; messageBytes[VERSION_OFFSET] = bundledMessageBytes[VERSION_OFFSET];
System.arraycopy(identityKeyBytes, 0, messageBytes, IDENTITY_KEY_OFFSET, identityKeyBytes.length); 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); System.arraycopy(bundledMessageBytes, VERSION_OFFSET+VERSION_LENGTH, messageBytes, IDENTITY_KEY_OFFSET+IDENTITY_KEY_LENGTH, bundledMessageBytes.length-VERSION_LENGTH);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
} }
public byte[] serialize() { public byte[] serialize() {
@ -96,26 +85,22 @@ public class PreKeyBundleMessage {
} }
public int getSupportedVersion() { public int getSupportedVersion() {
return supportedVersion; return bundledMessage.getSupportedVersion();
}
public int getMessageVersion() {
return messageVersion;
} }
public IdentityKey getIdentityKey() { public IdentityKey getIdentityKey() {
return identityKey; return identityKey;
} }
public PublicKey getPublicKey() { public PublicKey getPublicKey() throws InvalidKeyException {
return publicKey; return new PublicKey(bundledMessage.getNextKeyBytes());
} }
public byte[] getBundledMessage() { public CiphertextMessage getBundledMessage() {
return bundledMessage; return bundledMessage;
} }
public int getPreKeyId() { public int getPreKeyId() {
return preKeyId; return bundledMessage.getReceiverKeyId();
} }
} }

View File

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

View File

@ -84,32 +84,31 @@ public class PushServiceSocket {
makeRequest(REGISTER_GCM_PATH, "DELETE", null); makeRequest(REGISTER_GCM_PATH, "DELETE", null);
} }
public void sendMessage(PushDestination recipient, byte[] body, int type) public void sendMessage(PushDestination recipient, PushBody pushBody)
throws IOException throws IOException
{ {
OutgoingPushMessage message = new OutgoingPushMessage(recipient.getRelay(), OutgoingPushMessage message = new OutgoingPushMessage(recipient.getRelay(),
recipient.getNumber(), recipient.getNumber(),
body, type); pushBody.getBody(),
pushBody.getType());
sendMessage(new OutgoingPushMessageList(message)); sendMessage(new OutgoingPushMessageList(message));
} }
public void sendMessage(List<PushDestination> recipients, public void sendMessage(List<PushDestination> recipients, List<PushBody> bodies)
List<byte[]> bodies, List<Integer> types)
throws IOException throws IOException
{ {
List<OutgoingPushMessage> messages = new LinkedList<OutgoingPushMessage>(); List<OutgoingPushMessage> messages = new LinkedList<OutgoingPushMessage>();
Iterator<PushDestination> recipientsIterator = recipients.iterator(); Iterator<PushDestination> recipientsIterator = recipients.iterator();
Iterator<byte[]> bodiesIterator = bodies.iterator(); Iterator<PushBody> bodiesIterator = bodies.iterator();
Iterator<Integer> typesIterator = types.iterator();
while (recipientsIterator.hasNext()) { while (recipientsIterator.hasNext()) {
PushDestination recipient = recipientsIterator.next(); PushDestination recipient = recipientsIterator.next();
byte[] body = bodiesIterator.next(); PushBody body = bodiesIterator.next();
int type = typesIterator.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)); sendMessage(new OutgoingPushMessageList(messages));

View File

@ -40,6 +40,7 @@ import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.MemoryCleaner; import org.thoughtcrime.securesms.util.MemoryCleaner;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
import org.whispersystems.textsecure.storage.InvalidKeyIdException; import org.whispersystems.textsecure.storage.InvalidKeyIdException;
@ -182,9 +183,9 @@ public class ReceiveKeyActivity extends Activity {
} else if (keyExchangeMessageBundle != null) { } else if (keyExchangeMessageBundle != null) {
try { try {
keyExchangeProcessor.processKeyExchangeMessage(keyExchangeMessageBundle); keyExchangeProcessor.processKeyExchangeMessage(keyExchangeMessageBundle);
byte[] bundledMessage = keyExchangeMessageBundle.getBundledMessage(); CiphertextMessage bundledMessage = keyExchangeMessageBundle.getBundledMessage();
SmsTransportDetails transportDetails = new SmsTransportDetails(); SmsTransportDetails transportDetails = new SmsTransportDetails();
String messageBody = new String(transportDetails.getEncodedMessage(bundledMessage)); String messageBody = new String(transportDetails.getEncodedMessage(bundledMessage.serialize()));
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this) DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.updateBundleMessageBody(masterSecret, messageId, messageBody); .updateBundleMessageBody(masterSecret, messageId, messageBody);
@ -196,6 +197,10 @@ public class ReceiveKeyActivity extends Activity {
Log.w("ReceiveKeyActivity", e); Log.w("ReceiveKeyActivity", e);
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this) DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.markAsCorruptKeyExchange(messageId); .markAsCorruptKeyExchange(messageId);
} catch (InvalidKeyException e) {
Log.w("ReceiveKeyActivity", e);
DatabaseFactory.getEncryptingSmsDatabase(ReceiveKeyActivity.this)
.markAsCorruptKeyExchange(messageId);
} }
} }

View File

@ -48,7 +48,6 @@ import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.MessageCipher; import org.whispersystems.textsecure.crypto.MessageCipher;
import org.whispersystems.textsecure.crypto.SessionCipher; import org.whispersystems.textsecure.crypto.SessionCipher;
import org.whispersystems.textsecure.push.IncomingPushMessage; import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.push.PushTransportDetails;
import org.whispersystems.textsecure.util.Hex; import org.whispersystems.textsecure.util.Hex;
import java.io.IOException; import java.io.IOException;
@ -197,9 +196,9 @@ public class DecryptingQueue {
} }
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); 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); message = message.withBody(plaintextBody);
sendResult(PushReceiver.RESULT_OK); sendResult(PushReceiver.RESULT_OK);
} catch (InvalidMessageException e) { } catch (InvalidMessageException e) {
@ -276,11 +275,13 @@ public class DecryptingQueue {
synchronized (SessionCipher.CIPHER_LOCK) { synchronized (SessionCipher.CIPHER_LOCK) {
Log.w("DecryptingQueue", "Decrypting: " + Hex.toString(ciphertextPduBytes)); Log.w("DecryptingQueue", "Decrypting: " + Hex.toString(ciphertextPduBytes));
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); TextTransport transportDetails = new TextTransport();
MessageCipher message = new MessageCipher(context, masterSecret, identityKey, new TextTransport()); IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey);
byte[] ciphertext = transportDetails.getDecodedMessage(ciphertextPduBytes);
try { try {
plaintextPduBytes = message.decrypt(recipient, ciphertextPduBytes); plaintextPduBytes = messageCipher.decrypt(recipient, ciphertext);
} catch (InvalidMessageException ime) { } catch (InvalidMessageException ime) {
// XXX - For some reason, Sprint seems to append a single character to the // 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 // 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..."); Log.w("DecryptingQueue", "Attempting truncated decrypt...");
byte[] truncated = new byte[ciphertextPduBytes.length - 1]; byte[] truncated = new byte[ciphertextPduBytes.length - 1];
System.arraycopy(ciphertextPduBytes, 0, truncated, 0, truncated.length); System.arraycopy(ciphertextPduBytes, 0, truncated, 0, truncated.length);
plaintextPduBytes = message.decrypt(recipient, truncated); ciphertext = transportDetails.getDecodedMessage(truncated);
plaintextPduBytes = messageCipher.decrypt(recipient, ciphertext);
} else { } else {
throw ime; throw ime;
} }
@ -311,6 +313,9 @@ public class DecryptingQueue {
} catch (MmsException mme) { } catch (MmsException mme) {
Log.w("DecryptingQueue", mme); Log.w("DecryptingQueue", mme);
database.markAsDecryptFailed(messageId, threadId); database.markAsDecryptFailed(messageId, threadId);
} catch (IOException e) {
Log.w("DecryptingQueue", e);
database.markAsDecryptFailed(messageId, threadId);
} }
} }
} }
@ -354,10 +359,13 @@ public class DecryptingQueue {
return; return;
} }
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); SmsTransportDetails transportDetails = new SmsTransportDetails();
MessageCipher message = new MessageCipher(context, masterSecret, identityKey, 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) { } catch (InvalidMessageException e) {
Log.w("DecryptionQueue", e); Log.w("DecryptionQueue", e);
database.markAsDecryptFailed(messageId); database.markAsDecryptFailed(messageId);
@ -366,6 +374,10 @@ public class DecryptingQueue {
Log.w("DecryptionQueue", e); Log.w("DecryptionQueue", e);
database.markAsDecryptFailed(messageId); database.markAsDecryptFailed(messageId);
return; return;
} catch (IOException e) {
Log.w("DecryptionQueue", e);
database.markAsDecryptFailed(messageId);
return;
} }
} }

View File

@ -22,12 +22,17 @@ import android.util.Log;
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage; import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.database.DatabaseFactory; 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.IdentityKey;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.KeyPair; import org.whispersystems.textsecure.crypto.KeyPair;
import org.whispersystems.textsecure.crypto.KeyUtil; import org.whispersystems.textsecure.crypto.KeyUtil;
import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.MessageCipher;
import org.whispersystems.textsecure.crypto.PublicKey; import org.whispersystems.textsecure.crypto.PublicKey;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
import org.whispersystems.textsecure.push.PreKeyEntity; import org.whispersystems.textsecure.push.PreKeyEntity;
import org.whispersystems.textsecure.storage.InvalidKeyIdException; 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.PreKeyRecord;
import org.whispersystems.textsecure.storage.RemoteKeyRecord; import org.whispersystems.textsecure.storage.RemoteKeyRecord;
import org.whispersystems.textsecure.storage.SessionRecord; 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.Conversions;
import org.whispersystems.textsecure.util.Medium; import org.whispersystems.textsecure.util.Medium;
@ -103,7 +104,9 @@ public class KeyExchangeProcessor {
(localKeyRecord.getCurrentKeyPair() != null && localKeyRecord.getCurrentKeyPair().getId() != responseKeyId); (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(); int preKeyId = message.getPreKeyId();
PublicKey remoteKey = message.getPublicKey(); PublicKey remoteKey = message.getPublicKey();
IdentityKey remoteIdentity = message.getIdentityKey(); IdentityKey remoteIdentity = message.getIdentityKey();
@ -131,7 +134,7 @@ public class KeyExchangeProcessor {
sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(), sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(),
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes()); remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
sessionRecord.setIdentityKey(remoteIdentity); 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()); sessionRecord.setNegotiatedSessionVersion(sessionRecord.getSessionVersion());
localKeyRecord.save(); localKeyRecord.save();
@ -159,8 +162,8 @@ public class KeyExchangeProcessor {
sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(), sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(),
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes()); remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
sessionRecord.setIdentityKey(message.getIdentityKey()); sessionRecord.setIdentityKey(message.getIdentityKey());
sessionRecord.setNegotiatedSessionVersion(MessageCipher.SUPPORTED_VERSION); sessionRecord.setNegotiatedSessionVersion(CiphertextMessage.SUPPORTED_VERSION);
sessionRecord.setSessionVersion(MessageCipher.SUPPORTED_VERSION); sessionRecord.setSessionVersion(CiphertextMessage.SUPPORTED_VERSION);
sessionRecord.setPrekeyBundleRequired(true); sessionRecord.setPrekeyBundleRequired(true);
sessionRecord.save(); sessionRecord.save();
@ -174,7 +177,7 @@ public class KeyExchangeProcessor {
if (needsResponseFromUs()) { if (needsResponseFromUs()) {
localKeyRecord = KeyUtil.initializeRecordFor(recipient, context, masterSecret); 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()); OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, ourMessage.serialize());
Log.w("KeyExchangeProcessor", "Responding with key exchange message fingerprint: " + ourMessage.getPublicKey().getFingerprint()); 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()); 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(), sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(),
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes()); remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
sessionRecord.setIdentityKey(message.getIdentityKey()); 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()); 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(); sessionRecord.save();

View File

@ -20,12 +20,12 @@ import android.content.Context;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.whispersystems.textsecure.crypto.InvalidVersionException;
import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.InvalidVersionException;
import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.MessageCipher;
import org.whispersystems.textsecure.crypto.PublicKey; import org.whispersystems.textsecure.crypto.PublicKey;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.storage.LocalKeyRecord; import org.whispersystems.textsecure.storage.LocalKeyRecord;
import org.whispersystems.textsecure.util.Base64; import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.Conversions; import org.whispersystems.textsecure.util.Conversions;
@ -59,6 +59,8 @@ import java.io.IOException;
public class KeyExchangeMessage { public class KeyExchangeMessage {
private static final int SUPPORTED_VERSION = CiphertextMessage.SUPPORTED_VERSION;
private final int messageVersion; private final int messageVersion;
private final int supportedVersion; private final int supportedVersion;
private final PublicKey publicKey; private final PublicKey publicKey;
@ -68,7 +70,7 @@ public class KeyExchangeMessage {
public KeyExchangeMessage(Context context, MasterSecret masterSecret, int messageVersion, LocalKeyRecord record, int highIdBits) { public KeyExchangeMessage(Context context, MasterSecret masterSecret, int messageVersion, LocalKeyRecord record, int highIdBits) {
this.publicKey = new PublicKey(record.getCurrentKeyPair().getPublicKey()); this.publicKey = new PublicKey(record.getCurrentKeyPair().getPublicKey());
this.messageVersion = messageVersion; this.messageVersion = messageVersion;
this.supportedVersion = MessageCipher.SUPPORTED_VERSION; this.supportedVersion = SUPPORTED_VERSION;
publicKey.setId(publicKey.getId() | (highIdBits << 12)); publicKey.setId(publicKey.getId() | (highIdBits << 12));
@ -100,9 +102,9 @@ public class KeyExchangeMessage {
this.supportedVersion = Conversions.lowBitsToInt(keyBytes[0]); this.supportedVersion = Conversions.lowBitsToInt(keyBytes[0]);
this.serialized = messageBody; this.serialized = messageBody;
if (messageVersion > MessageCipher.SUPPORTED_VERSION) if (messageVersion > SUPPORTED_VERSION)
throw new InvalidVersionException("Key exchange with version: " + messageVersion + throw new InvalidVersionException("Key exchange with version: " + messageVersion +
" but we only support: " + MessageCipher.SUPPORTED_VERSION); " but we only support: " + SUPPORTED_VERSION);
if (messageVersion >= 1) if (messageVersion >= 1)
keyBytes = Base64.decodeWithoutPadding(messageBody); keyBytes = Base64.decodeWithoutPadding(messageBody);

View File

@ -6,6 +6,7 @@ import android.util.Log;
import android.util.Pair; import android.util.Pair;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import org.thoughtcrime.securesms.crypto.DecryptingQueue; import org.thoughtcrime.securesms.crypto.DecryptingQueue;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
import org.thoughtcrime.securesms.database.DatabaseFactory; 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.IncomingPreKeyBundleMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.SmsTransportDetails; import org.thoughtcrime.securesms.sms.SmsTransportDetails;
import org.thoughtcrime.securesms.transport.SmsTransport;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.InvalidVersionException; import org.whispersystems.textsecure.crypto.InvalidVersionException;
@ -99,7 +99,7 @@ public class PushReceiver {
if (processor.isTrusted(preKeyExchange)) { if (processor.isTrusted(preKeyExchange)) {
processor.processKeyExchangeMessage(preKeyExchange); processor.processKeyExchangeMessage(preKeyExchange);
IncomingPushMessage bundledMessage = message.withBody(preKeyExchange.getBundledMessage()); IncomingPushMessage bundledMessage = message.withBody(preKeyExchange.getBundledMessage().serialize());
handleReceivedSecureMessage(masterSecret, bundledMessage); handleReceivedSecureMessage(masterSecret, bundledMessage);
} else { } else {
SmsTransportDetails transportDetails = new SmsTransportDetails(); SmsTransportDetails transportDetails = new SmsTransportDetails();

View File

@ -40,6 +40,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.InvalidVersionException; import org.whispersystems.textsecure.crypto.InvalidVersionException;
import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
import org.whispersystems.textsecure.storage.InvalidKeyIdException; import org.whispersystems.textsecure.storage.InvalidKeyIdException;
@ -110,7 +111,8 @@ public class SmsReceiver {
if (processor.isTrusted(preKeyExchange)) { if (processor.isTrusted(preKeyExchange)) {
processor.processKeyExchangeMessage(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); IncomingEncryptedMessage bundledMessage = new IncomingEncryptedMessage(message, bundledMessageBody);
Pair<Long, Long> messageAndThreadId = storeSecureMessage(masterSecret, bundledMessage); Pair<Long, Long> messageAndThreadId = storeSecureMessage(masterSecret, bundledMessage);

View File

@ -18,9 +18,9 @@ package org.thoughtcrime.securesms.sms;
import android.util.Log; import android.util.Log;
import org.whispersystems.textsecure.crypto.SessionCipher;
import org.whispersystems.textsecure.crypto.TransportDetails;
import org.thoughtcrime.securesms.protocol.WirePrefix; 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 org.whispersystems.textsecure.util.Base64;
import java.io.IOException; 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 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 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 @Override
public byte[] getEncodedMessage(byte[] messageWithMac) { public byte[] getEncodedMessage(byte[] messageWithMac) {
@ -73,7 +73,7 @@ public class SmsTransportDetails implements TransportDetails {
@Override @Override
public byte[] getPaddedMessageBody(byte[] messageBody) { public byte[] getPaddedMessageBody(byte[] messageBody) {
int paddedBodySize = getMaxBodySizeForBytes(messageBody.length); 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]; byte[] paddedBody = new byte[paddedBodySize];
System.arraycopy(messageBody, 0, paddedBody, 0, messageBody.length); System.arraycopy(messageBody, 0, paddedBody, 0, messageBody.length);
@ -82,7 +82,7 @@ public class SmsTransportDetails implements TransportDetails {
} }
private int getMaxBodySizeForBytes(int bodyLength) { private int getMaxBodySizeForBytes(int bodyLength) {
int encryptedBodyLength = bodyLength + SessionCipher.ENCRYPTED_MESSAGE_OVERHEAD; int encryptedBodyLength = bodyLength + CiphertextMessage.ENCRYPTED_MESSAGE_OVERHEAD;
int messageRecordsForBody = getMessageCountForBytes(encryptedBodyLength); int messageRecordsForBody = getMessageCountForBytes(encryptedBodyLength);
if (messageRecordsForBody == 1) { if (messageRecordsForBody == 1) {
@ -91,7 +91,7 @@ public class SmsTransportDetails implements TransportDetails {
return return
FIRST_MULTI_MESSAGE_MAX_BYTES + FIRST_MULTI_MESSAGE_MAX_BYTES +
(MULTI_MESSAGE_MAX_BYTES * (messageRecordsForBody-1)) - (MULTI_MESSAGE_MAX_BYTES * (messageRecordsForBody-1)) -
SessionCipher.ENCRYPTED_MESSAGE_OVERHEAD; CiphertextMessage.ENCRYPTED_MESSAGE_OVERHEAD;
} }
} }

View File

@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.mms.MmsSendHelper;
import org.thoughtcrime.securesms.mms.TextTransport; import org.thoughtcrime.securesms.mms.TextTransport;
import org.thoughtcrime.securesms.protocol.WirePrefix; import org.thoughtcrime.securesms.protocol.WirePrefix;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.util.Hex; import org.whispersystems.textsecure.util.Hex;
import java.io.IOException; import java.io.IOException;
@ -134,10 +135,14 @@ public class MmsTransport {
return encryptedPdu; return encryptedPdu;
} }
private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipient, byte[] pduBytes) { private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipientString, byte[] pduBytes) {
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); TextTransport transportDetails = new TextTransport();
MessageCipher message = new MessageCipher(context, masterSecret, identityKey, new TextTransport()); Recipient recipient = new Recipient(null, recipientString, null, null);
return message.encrypt(new Recipient(null, recipient, null, null), pduBytes); 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) { private boolean isInconsistentResponse(SendReq message, SendConf response) {

View File

@ -2,9 +2,9 @@ package org.thoughtcrime.securesms.transport;
import android.content.Context; import android.content.Context;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord; 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.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.TextSecurePushCredentials; import org.thoughtcrime.securesms.util.TextSecurePushCredentials;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.textsecure.crypto.AttachmentCipher; 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.KeyUtil;
import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.MessageCipher; import org.whispersystems.textsecure.crypto.MessageCipher;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage; import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
import org.whispersystems.textsecure.directory.Directory;
import org.whispersystems.textsecure.push.OutgoingPushMessage; import org.whispersystems.textsecure.push.OutgoingPushMessage;
import org.whispersystems.textsecure.push.PreKeyEntity; import org.whispersystems.textsecure.push.PreKeyEntity;
import org.whispersystems.textsecure.push.PushAttachmentData; import org.whispersystems.textsecure.push.PushAttachmentData;
import org.whispersystems.textsecure.push.PushAttachmentPointer; import org.whispersystems.textsecure.push.PushAttachmentPointer;
import org.whispersystems.textsecure.push.PushBody;
import org.whispersystems.textsecure.push.PushDestination; import org.whispersystems.textsecure.push.PushDestination;
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.RateLimitException; import org.whispersystems.textsecure.push.RateLimitException;
import org.whispersystems.textsecure.push.RawTransportDetails;
import org.whispersystems.textsecure.util.PhoneNumberFormatter;
import java.io.IOException; import java.io.IOException;
import java.util.LinkedList; import java.util.LinkedList;
@ -62,14 +59,11 @@ public class PushTransport extends BaseTransport {
PushDestination destination = PushDestination.create(context, credentials, PushDestination destination = PushDestination.create(context, credentials,
recipient.getNumber()); recipient.getNumber());
String plaintextBody = message.getBody().getBody(); String plaintextBody = message.getBody().getBody();
byte[] plaintext = PushMessageContent.newBuilder() byte[] plaintext = PushMessageContent.newBuilder().setBody(plaintextBody).build().toByteArray();
.setBody(plaintextBody) PushBody pushBody = getEncryptedMessage(socket, recipient, destination, plaintext);
.build().toByteArray();
Pair<Integer, byte[]> typeAndCiphertext = getEncryptedMessage(socket, recipient, destination, plaintext); socket.sendMessage(destination, pushBody);
socket.sendMessage(destination, typeAndCiphertext.second, typeAndCiphertext.first);
context.sendBroadcast(constructSentIntent(context, message.getId(), message.getType())); context.sendBroadcast(constructSentIntent(context, message.getId(), message.getType()));
} catch (RateLimitException e) { } catch (RateLimitException e) {
@ -83,9 +77,7 @@ public class PushTransport extends BaseTransport {
TextSecurePushCredentials credentials = TextSecurePushCredentials.getInstance(); TextSecurePushCredentials credentials = TextSecurePushCredentials.getInstance();
PushServiceSocket socket = new PushServiceSocket(context, credentials); PushServiceSocket socket = new PushServiceSocket(context, credentials);
String messageBody = PartParser.getMessageText(message.getBody()); String messageBody = PartParser.getMessageText(message.getBody());
List<PushBody> pushBodies = new LinkedList<PushBody>();
List<byte[]> ciphertext = new LinkedList<byte[]>();
List<Integer> types = new LinkedList<Integer>();
for (PushDestination destination : destinations) { for (PushDestination destination : destinations) {
Recipients recipients = RecipientFactory.getRecipientsFromString(context, destination.getNumber(), false); Recipients recipients = RecipientFactory.getRecipientsFromString(context, destination.getNumber(), false);
@ -107,15 +99,13 @@ public class PushTransport extends BaseTransport {
builder.addAttachments(attachmentBuilder.build()); builder.addAttachments(attachmentBuilder.build());
} }
byte[] plaintext = builder.build().toByteArray(); byte[] plaintext = builder.build().toByteArray();
Pair<Integer, byte[]> typeAndCiphertext = getEncryptedMessage(socket, recipients.getPrimaryRecipient(), PushBody pushBody = getEncryptedMessage(socket, recipients.getPrimaryRecipient(), destination, plaintext);
destination, plaintext);
types.add(typeAndCiphertext.first); pushBodies.add(pushBody);
ciphertext.add(typeAndCiphertext.second);
} }
socket.sendMessage(destinations, ciphertext, types); socket.sendMessage(destinations, pushBodies);
} catch (RateLimitException e) { } catch (RateLimitException e) {
Log.w("PushTransport", e); Log.w("PushTransport", e);
@ -150,37 +140,34 @@ public class PushTransport extends BaseTransport {
return attachments; return attachments;
} }
private Pair<Integer, byte[]> getEncryptedMessage(PushServiceSocket socket, private PushBody getEncryptedMessage(PushServiceSocket socket, Recipient recipient,
Recipient recipient, PushDestination pushDestination, byte[] plaintext)
PushDestination pushDestination,
byte[] plaintext)
throws IOException throws IOException
{ {
if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) { if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) {
Log.w("PushTransport", "Sending standard ciphertext message..."); Log.w("PushTransport", "Sending standard ciphertext message...");
byte[] ciphertext = getEncryptedMessageForExistingSession(recipient, plaintext); byte[] ciphertext = getEncryptedMessageForExistingSession(recipient, plaintext);
return new Pair<Integer, byte[]>(OutgoingPushMessage.TYPE_MESSAGE_CIPHERTEXT, ciphertext); return new PushBody(OutgoingPushMessage.TYPE_MESSAGE_CIPHERTEXT, ciphertext);
} else if (KeyUtil.isSessionFor(context, recipient)) { } else if (KeyUtil.isSessionFor(context, recipient)) {
Log.w("PushTransport", "Sending prekeybundle ciphertext message for existing session..."); Log.w("PushTransport", "Sending prekeybundle ciphertext message for existing session...");
byte[] ciphertext = getEncryptedPrekeyBundleMessageForExistingSession(recipient, plaintext); byte[] ciphertext = getEncryptedPrekeyBundleMessageForExistingSession(recipient, plaintext);
return new Pair<Integer, byte[]>(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext); return new PushBody(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext);
} else { } else {
Log.w("PushTransport", "Sending prekeybundle ciphertext message for new session..."); Log.w("PushTransport", "Sending prekeybundle ciphertext message for new session...");
byte[] ciphertext = getEncryptedPrekeyBundleMessageForNewSession(socket, recipient, pushDestination, plaintext); byte[] ciphertext = getEncryptedPrekeyBundleMessageForNewSession(socket, recipient, pushDestination, plaintext);
return new Pair<Integer, byte[]>(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext); return new PushBody(OutgoingPushMessage.TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext);
} }
} }
private byte[] getEncryptedPrekeyBundleMessageForExistingSession(Recipient recipient, private byte[] getEncryptedPrekeyBundleMessageForExistingSession(Recipient recipient,
byte[] plaintext) byte[] plaintext)
{ {
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
IdentityKey identityKey = identityKeyPair.getPublicKey(); IdentityKey identityKey = identityKeyPair.getPublicKey();
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair);
CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, plaintext);
PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(ciphertextMessage, identityKey);
MessageCipher message = new MessageCipher(context, masterSecret, identityKeyPair, new RawTransportDetails());
byte[] bundledMessage = message.encrypt(recipient, plaintext);
PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(identityKey, bundledMessage);
return preKeyBundleMessage.serialize(); return preKeyBundleMessage.serialize();
} }
@ -197,21 +184,21 @@ public class PushTransport extends BaseTransport {
processor.processKeyExchangeMessage(preKey); processor.processKeyExchangeMessage(preKey);
MessageCipher message = new MessageCipher(context, masterSecret, identityKeyPair, new RawTransportDetails()); MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair);
byte[] bundledMessage = message.encrypt(recipient, plaintext); CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, plaintext);
PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(ciphertextMessage, identityKey);
PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(identityKey, bundledMessage);
return preKeyBundleMessage.serialize(); return preKeyBundleMessage.serialize();
} }
private byte[] getEncryptedMessageForExistingSession(Recipient recipient, byte[] plaintext) private byte[] getEncryptedMessageForExistingSession(Recipient recipient, byte[] plaintext)
throws IOException throws IOException
{ {
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair, MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair);
new PushTransportDetails()); CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, plaintext);
return messageCipher.encrypt(recipient, plaintext); return ciphertextMessage.serialize();
} }
} }

View File

@ -6,21 +6,21 @@ import android.telephony.SmsManager;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; 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.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.SendReceiveService; import org.thoughtcrime.securesms.service.SendReceiveService;
import org.thoughtcrime.securesms.service.SmsDeliveryListener; import org.thoughtcrime.securesms.service.SmsDeliveryListener;
import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler; import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler;
import org.thoughtcrime.securesms.sms.OutgoingPrekeyBundleMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.sms.SmsTransportDetails; import org.thoughtcrime.securesms.sms.SmsTransportDetails;
import org.thoughtcrime.securesms.util.TextSecurePreferences; 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.crypto.protocol.PreKeyBundleMessage;
import org.whispersystems.textsecure.util.Base64;
import java.util.ArrayList; import java.util.ArrayList;
@ -144,23 +144,28 @@ public class SmsTransport extends BaseTransport {
private OutgoingTextMessage getAsymmetricEncrypt(MasterSecret masterSecret, private OutgoingTextMessage getAsymmetricEncrypt(MasterSecret masterSecret,
OutgoingTextMessage message) OutgoingTextMessage message)
{ {
Recipient recipient = message.getRecipients().getPrimaryRecipient(); Recipient recipient = message.getRecipients().getPrimaryRecipient();
String body = message.getMessageBody(); String body = message.getMessageBody();
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
SmsTransportDetails transportDetails = new SmsTransportDetails();
if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) { if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) {
Log.w("SmsTransport", "Delivering standard ciphertext..."); 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 { } else {
Log.w("SmsTransport", "Delivering prekeybundle ciphertext..."); Log.w("SmsTransport", "Delivering prekeybundle ciphertext...");
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey, new RawTransportDetails()); MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey);
byte[] bundledMessage = messageCipher.encrypt(recipient, body.getBytes()); CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, body.getBytes());
PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(identityKey.getPublicKey(), bundledMessage); 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)));
} }
} }
} }