diff --git a/library/src/org/whispersystems/textsecure/Release.java b/library/src/org/whispersystems/textsecure/Release.java
index 13d5b78824..07cebf4ffa 100644
--- a/library/src/org/whispersystems/textsecure/Release.java
+++ b/library/src/org/whispersystems/textsecure/Release.java
@@ -1,7 +1,7 @@
package org.whispersystems.textsecure;
public class Release {
- public static final String PUSH_SERVICE_URL = "https://gcm.textsecure.whispersystems.org";
-// public static final String PUSH_SERVICE_URL = "http://192.168.1.135:8080";
- public static final boolean ENFORCE_SSL = true;
+// public static final String PUSH_SERVICE_URL = "https://gcm.textsecure.whispersystems.org";
+ public static final String PUSH_SERVICE_URL = "http://192.168.1.135:8080";
+ public static final boolean ENFORCE_SSL = false;
}
diff --git a/library/src/org/whispersystems/textsecure/crypto/IdentityKeyPair.java b/library/src/org/whispersystems/textsecure/crypto/IdentityKeyPair.java
new file mode 100644
index 0000000000..643e752f59
--- /dev/null
+++ b/library/src/org/whispersystems/textsecure/crypto/IdentityKeyPair.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (C) 2013 Open Whisper Systems
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.whispersystems.textsecure.crypto;
+
+import org.spongycastle.crypto.params.ECPrivateKeyParameters;
+
+/**
+ * Holder for public and private identity key pair.
+ *
+ * @author Moxie Marlinspike
+ */
+public class IdentityKeyPair {
+
+ private final IdentityKey publicKey;
+ private final ECPrivateKeyParameters privateKey;
+
+ public IdentityKeyPair(IdentityKey publicKey, ECPrivateKeyParameters privateKey) {
+ this.publicKey = publicKey;
+ this.privateKey = privateKey;
+ }
+
+ public IdentityKey getPublicKey() {
+ return publicKey;
+ }
+
+ public ECPrivateKeyParameters getPrivateKey() {
+ return privateKey;
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/crypto/InvalidVersionException.java b/library/src/org/whispersystems/textsecure/crypto/InvalidVersionException.java
similarity index 96%
rename from src/org/thoughtcrime/securesms/crypto/InvalidVersionException.java
rename to library/src/org/whispersystems/textsecure/crypto/InvalidVersionException.java
index 2213ab2a9e..6194b1c3c9 100644
--- a/src/org/thoughtcrime/securesms/crypto/InvalidVersionException.java
+++ b/library/src/org/whispersystems/textsecure/crypto/InvalidVersionException.java
@@ -14,7 +14,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package org.thoughtcrime.securesms.crypto;
+package org.whispersystems.textsecure.crypto;
public class InvalidVersionException extends Exception {
diff --git a/library/src/org/whispersystems/textsecure/crypto/PreKeyPair.java b/library/src/org/whispersystems/textsecure/crypto/PreKeyPair.java
index 0e24d904bc..3b03500560 100644
--- a/library/src/org/whispersystems/textsecure/crypto/PreKeyPair.java
+++ b/library/src/org/whispersystems/textsecure/crypto/PreKeyPair.java
@@ -33,6 +33,10 @@ public class PreKeyPair {
return publicKey;
}
+ public AsymmetricCipherKeyPair getKeyPair() {
+ return new AsymmetricCipherKeyPair(publicKey.getPublicKey(), privateKey);
+ }
+
public byte[] serialize() {
byte[] publicKeyBytes = publicKey.serialize();
byte[] privateKeyBytes = masterCipher.encryptKey(privateKey);
diff --git a/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java b/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java
index c93cd8831b..24fdfa2477 100644
--- a/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java
+++ b/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java
@@ -42,6 +42,9 @@ import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
/**
* This is where the session encryption magic happens. Implements a compressed version of the OTR protocol.
@@ -58,35 +61,42 @@ public class SessionCipher {
public static final int ENCRYPTED_MESSAGE_OVERHEAD = EncryptedMessage.HEADER_LENGTH + MessageMac.MAC_LENGTH;
- public SessionCipherContext getEncryptionContext(Context context, MasterSecret masterSecret,
+ public SessionCipherContext getEncryptionContext(Context context,
+ MasterSecret masterSecret,
+ IdentityKeyPair localIdentityKey,
CanonicalRecipientAddress recipient)
{
try {
KeyRecords records = getKeyRecords(context, masterSecret, recipient);
int localKeyId = records.getLocalKeyRecord().getCurrentKeyPair().getId();
int remoteKeyId = records.getRemoteKeyRecord().getCurrentRemoteKey().getId();
- SessionKey sessionKey = getSessionKey(masterSecret, Cipher.ENCRYPT_MODE, records, localKeyId, remoteKeyId);
+ int negotiatedVersion = records.getSessionRecord().getSessionVersion();
+ SessionKey sessionKey = getSessionKey(masterSecret, Cipher.ENCRYPT_MODE, negotiatedVersion, localIdentityKey, records, localKeyId, remoteKeyId);
PublicKey nextKey = records.getLocalKeyRecord().getNextKeyPair().getPublicKey();
int counter = records.getSessionRecord().getCounter();
- int negotiatedVersion = records.getSessionRecord().getSessionVersion();
- return new SessionCipherContext(records, sessionKey, localKeyId, remoteKeyId, nextKey, counter, negotiatedVersion);
+
+ return new SessionCipherContext(records, sessionKey, localKeyId, remoteKeyId,
+ nextKey, counter, negotiatedVersion, negotiatedVersion);
} catch (InvalidKeyIdException e) {
throw new IllegalArgumentException(e);
}
}
public SessionCipherContext getDecryptionContext(Context context, MasterSecret masterSecret,
+ IdentityKeyPair localIdentityKey,
CanonicalRecipientAddress recipient,
int senderKeyId, int recipientKeyId,
PublicKey nextKey, int counter,
- int negotiatedVersion)
+ int messageVersion, int negotiatedVersion)
throws InvalidMessageException
{
try {
KeyRecords records = getKeyRecords(context, masterSecret, recipient);
- SessionKey sessionKey = getSessionKey(masterSecret, Cipher.DECRYPT_MODE, records, recipientKeyId, senderKeyId);
- return new SessionCipherContext(records, sessionKey, senderKeyId, recipientKeyId, nextKey, counter, negotiatedVersion);
+ SessionKey sessionKey = getSessionKey(masterSecret, Cipher.DECRYPT_MODE, messageVersion, localIdentityKey, records, recipientKeyId, senderKeyId);
+ return new SessionCipherContext(records, sessionKey, senderKeyId,
+ recipientKeyId, nextKey, counter,
+ messageVersion, negotiatedVersion);
} catch (InvalidKeyIdException e) {
throw new InvalidMessageException(e);
}
@@ -193,12 +203,12 @@ public class SessionCipher {
}
}
- private SecretKeySpec deriveCipherSecret(int mode, BigInteger sharedSecret,
+ private SecretKeySpec deriveCipherSecret(int mode, List sharedSecret,
KeyRecords records, int localKeyId,
int remoteKeyId)
throws InvalidKeyIdException
{
- byte[] sharedSecretBytes = sharedSecret.toByteArray();
+ byte[] sharedSecretBytes = concatenateSharedSecrets(sharedSecret);
byte[] derivedBytes = deriveBytes(sharedSecretBytes, 16 * 2);
byte[] cipherSecret = new byte[16];
@@ -213,18 +223,40 @@ public class SessionCipher {
return new SecretKeySpec(cipherSecret, "AES");
}
+
+ private byte[] concatenateSharedSecrets(List sharedSecrets) {
+ int totalByteSize = 0;
+ List byteValues = new LinkedList();
+
+ for (BigInteger sharedSecret : sharedSecrets) {
+ byte[] byteValue = sharedSecret.toByteArray();
+ totalByteSize += byteValue.length;
+ byteValues.add(byteValue);
+ }
+
+ byte[] combined = new byte[totalByteSize];
+ int offset = 0;
+
+ for (byte[] byteValue : byteValues) {
+ System.arraycopy(byteValue, 0, combined, offset, byteValue.length);
+ offset += byteValue.length;
+ }
+
+ return combined;
+ }
private boolean isLowEnd(KeyRecords records, int localKeyId, int remoteKeyId)
throws InvalidKeyIdException
{
ECPublicKeyParameters localPublic = records.getLocalKeyRecord().getKeyPairForId(localKeyId).getPublicKey().getKey();
ECPublicKeyParameters remotePublic = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey();
+
BigInteger local = localPublic.getQ().getX().toBigInteger();
BigInteger remote = remotePublic.getQ().getX().toBigInteger();
-
+
return local.compareTo(remote) < 0;
}
-
+
private byte[] deriveBytes(byte[] seed, int bytesNeeded) {
MessageDigest md;
@@ -246,7 +278,10 @@ public class SessionCipher {
return md.digest();
}
- private SessionKey getSessionKey(MasterSecret masterSecret, int mode, KeyRecords records,
+ private SessionKey getSessionKey(MasterSecret masterSecret, int mode,
+ int messageVersion,
+ IdentityKeyPair localIdentityKey,
+ KeyRecords records,
int localKeyId, int remoteKeyId)
throws InvalidKeyIdException
{
@@ -256,23 +291,41 @@ public class SessionCipher {
if (sessionKey != null)
return sessionKey;
- BigInteger sharedSecret = calculateSharedSecret(records, localKeyId, remoteKeyId);
- SecretKeySpec cipherKey = deriveCipherSecret(mode, sharedSecret, records, localKeyId, remoteKeyId);
- SecretKeySpec macKey = deriveMacSecret(cipherKey);
+ List sharedSecret = calculateSharedSecret(messageVersion, localIdentityKey, records, localKeyId, remoteKeyId);
+ SecretKeySpec cipherKey = deriveCipherSecret(mode, sharedSecret, records, localKeyId, remoteKeyId);
+ SecretKeySpec macKey = deriveMacSecret(cipherKey);
return new SessionKey(localKeyId, remoteKeyId, cipherKey, macKey, masterSecret);
}
- private BigInteger calculateSharedSecret(KeyRecords records, int localKeyId, int remoteKeyId)
+ private List calculateSharedSecret(int messageVersion,
+ IdentityKeyPair localIdentityKey,
+ KeyRecords records,
+ int localKeyId, int remoteKeyId)
throws InvalidKeyIdException
{
- ECDHBasicAgreement agreement = new ECDHBasicAgreement();
- AsymmetricCipherKeyPair localKeyPair = records.getLocalKeyRecord().getKeyPairForId(localKeyId).getKeyPair();
- ECPublicKeyParameters remoteKey = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey();
-
- agreement.init(localKeyPair.getPrivate());
+ KeyPair localKeyPair = records.getLocalKeyRecord().getKeyPairForId(localKeyId);
+ ECPublicKeyParameters remoteKey = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey();
+ IdentityKey remoteIdentityKey = records.getSessionRecord().getIdentityKey();
- return KeyUtil.calculateAgreement(agreement, remoteKey);
+ if (isInitiallyExchangedKeys(records, localKeyId, remoteKeyId) &&
+ messageVersion >= EncryptedMessage.CRADLE_AGREEMENT_VERSION)
+ {
+ return SharedSecretCalculator.calculateSharedSecret(localKeyPair, localIdentityKey,
+ remoteKey, remoteIdentityKey);
+ } else {
+ return SharedSecretCalculator.calculateSharedSecret(localKeyPair, remoteKey);
+ }
+ }
+
+ private boolean isInitiallyExchangedKeys(KeyRecords records, int localKeyId, int remoteKeyId)
+ throws InvalidKeyIdException
+ {
+ byte[] localFingerprint = records.getSessionRecord().getLocalFingerprint();
+ byte[] remoteFingerprint = records.getSessionRecord().getRemoteFingerprint();
+
+ return Arrays.equals(localFingerprint, records.getLocalKeyRecord().getKeyPairForId(localKeyId).getPublicKey().getFingerprintBytes()) &&
+ Arrays.equals(remoteFingerprint, records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getFingerprintBytes());
}
private KeyRecords getKeyRecords(Context context, MasterSecret masterSecret,
@@ -285,6 +338,7 @@ public class SessionCipher {
}
private static class KeyRecords {
+
private final LocalKeyRecord localKeyRecord;
private final RemoteKeyRecord remoteKeyRecord;
private final SessionRecord sessionRecord;
@@ -309,6 +363,7 @@ public class SessionCipher {
}
public static class SessionCipherContext {
+
private final LocalKeyRecord localKeyRecord;
private final RemoteKeyRecord remoteKeyRecord;
private final SessionRecord sessionRecord;
@@ -317,6 +372,7 @@ public class SessionCipher {
private final int recipientKeyId;
private final PublicKey nextKey;
private final int counter;
+ private final int messageVersion;
private final int negotiatedVersion;
public SessionCipherContext(KeyRecords records,
@@ -325,6 +381,7 @@ public class SessionCipher {
int receiverKeyId,
PublicKey nextKey,
int counter,
+ int messageVersion,
int negotiatedVersion)
{
this.localKeyRecord = records.getLocalKeyRecord();
@@ -335,6 +392,7 @@ public class SessionCipher {
this.recipientKeyId = receiverKeyId;
this.nextKey = nextKey;
this.counter = counter;
+ this.messageVersion = messageVersion;
this.negotiatedVersion = negotiatedVersion;
}
@@ -373,6 +431,10 @@ public class SessionCipher {
public int getNegotiatedVersion() {
return negotiatedVersion;
}
+
+ public int getMessageVersion() {
+ return messageVersion;
+ }
}
}
diff --git a/library/src/org/whispersystems/textsecure/crypto/SharedSecretCalculator.java b/library/src/org/whispersystems/textsecure/crypto/SharedSecretCalculator.java
new file mode 100644
index 0000000000..2c51df6a41
--- /dev/null
+++ b/library/src/org/whispersystems/textsecure/crypto/SharedSecretCalculator.java
@@ -0,0 +1,68 @@
+package org.whispersystems.textsecure.crypto;
+
+import android.util.Log;
+
+import org.spongycastle.crypto.CipherParameters;
+import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
+import org.spongycastle.crypto.params.ECPublicKeyParameters;
+
+import java.math.BigInteger;
+import java.util.LinkedList;
+import java.util.List;
+
+public class SharedSecretCalculator {
+
+ public static List calculateSharedSecret(KeyPair localKeyPair,
+ IdentityKeyPair localIdentityKeyPair,
+ ECPublicKeyParameters remoteKey,
+ IdentityKey remoteIdentityKey)
+ {
+ Log.w("SharedSecretCalculator", "Calculating shared secret with cradle agreement...");
+ List results = new LinkedList();
+
+ if (isLowEnd(localKeyPair.getPublicKey().getKey(), remoteKey)) {
+ results.add(calculateAgreement(localIdentityKeyPair.getPrivateKey(), remoteKey));
+
+ results.add(calculateAgreement(localKeyPair.getKeyPair().getPrivate(),
+ remoteIdentityKey.getPublicKeyParameters()));
+ } else {
+ results.add(calculateAgreement(localKeyPair.getKeyPair().getPrivate(),
+ remoteIdentityKey.getPublicKeyParameters()));
+
+ results.add(calculateAgreement(localIdentityKeyPair.getPrivateKey(), remoteKey));
+ }
+
+ results.add(calculateAgreement(localKeyPair.getKeyPair().getPrivate(), remoteKey));
+ return results;
+ }
+
+ public static List calculateSharedSecret(KeyPair localKeyPair,
+ ECPublicKeyParameters remoteKey)
+ {
+ Log.w("SharedSecretCalculator", "Calculating shared secret with standard agreement...");
+ List results = new LinkedList();
+ results.add(calculateAgreement(localKeyPair.getKeyPair().getPrivate(), remoteKey));
+
+ return results;
+ }
+
+ private static BigInteger calculateAgreement(CipherParameters privateKey,
+ ECPublicKeyParameters publicKey)
+ {
+ ECDHBasicAgreement agreement = new ECDHBasicAgreement();
+ agreement.init(privateKey);
+
+ return KeyUtil.calculateAgreement(agreement, publicKey);
+ }
+
+
+ private static boolean isLowEnd(ECPublicKeyParameters localPublic,
+ ECPublicKeyParameters remotePublic)
+ {
+ BigInteger local = localPublic.getQ().getX().toBigInteger();
+ BigInteger remote = remotePublic.getQ().getX().toBigInteger();
+
+ return local.compareTo(remote) < 0;
+ }
+
+}
diff --git a/library/src/org/whispersystems/textsecure/crypto/protocol/EncryptedMessage.java b/library/src/org/whispersystems/textsecure/crypto/protocol/EncryptedMessage.java
index 13ad15ecbc..9383a373bd 100644
--- a/library/src/org/whispersystems/textsecure/crypto/protocol/EncryptedMessage.java
+++ b/library/src/org/whispersystems/textsecure/crypto/protocol/EncryptedMessage.java
@@ -17,16 +17,20 @@
package org.whispersystems.textsecure.crypto.protocol;
import android.content.Context;
+import android.util.Log;
+import org.whispersystems.textsecure.crypto.IdentityKeyPair;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.InvalidMessageException;
import org.whispersystems.textsecure.crypto.MasterSecret;
+import org.whispersystems.textsecure.crypto.MessageMac;
import org.whispersystems.textsecure.crypto.PublicKey;
import org.whispersystems.textsecure.crypto.SessionCipher;
import org.whispersystems.textsecure.crypto.SessionCipher.SessionCipherContext;
import org.whispersystems.textsecure.crypto.TransportDetails;
import org.whispersystems.textsecure.storage.CanonicalRecipientAddress;
import org.whispersystems.textsecure.util.Conversions;
+import org.whispersystems.textsecure.util.Hex;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -39,29 +43,35 @@ import java.nio.ByteBuffer;
public class EncryptedMessage {
- public static final int SUPPORTED_VERSION = 1;
+ public static final int SUPPORTED_VERSION = 2;
+ public static final int CRADLE_AGREEMENT_VERSION = 2;
- private static final int VERSION_LENGTH = 1;
+ 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;
+ 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;
- private static final int VERSION_OFFSET = 0;
+ 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;
+ static final int RECEIVER_KEY_ID_OFFSET = SENDER_KEY_ID_OFFSET + SENDER_KEY_ID_LENGTH;
+ 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 EncryptedMessage(Context context, MasterSecret masterSecret, TransportDetails transportDetails) {
+ public EncryptedMessage(Context context, MasterSecret masterSecret,
+ IdentityKeyPair localIdentityKey,
+ TransportDetails transportDetails)
+ {
this.context = context.getApplicationContext();
this.masterSecret = masterSecret;
+ this.localIdentityKey = localIdentityKey;
this.transportDetails = transportDetails;
}
@@ -69,7 +79,7 @@ public class EncryptedMessage {
synchronized (SessionCipher.CIPHER_LOCK) {
byte[] paddedBody = transportDetails.getPaddedMessageBody(plaintext);
SessionCipher sessionCipher = new SessionCipher();
- SessionCipherContext sessionContext = sessionCipher.getEncryptionContext(context, masterSecret, recipient);
+ 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);
@@ -84,25 +94,33 @@ public class EncryptedMessage {
synchronized (SessionCipher.CIPHER_LOCK) {
try {
byte[] decodedMessage = transportDetails.getDecodedMessage(ciphertext);
- int messageVersion = getMessageVersion(decodedMessage);
+
+ if (decodedMessage.length <= HEADER_LENGTH) {
+ throw new InvalidMessageException("Message is shorter than headers");
+ }
+
+ 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 version = Math.min(supportedVersion, SUPPORTED_VERSION);
- SessionCipher sessionCipher = new SessionCipher();
- SessionCipherContext sessionContext = sessionCipher.getDecryptionContext(context, masterSecret,
- recipient, senderKeyId,
- receiverKeyId,
- nextRemoteKey,
- counter, version);
+ 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,
+ recipient, senderKeyId,
+ receiverKeyId,
+ nextRemoteKey,
+ counter,
+ messageVersion,
+ negotiatedVersion);
sessionCipher.verifyMac(sessionContext, decodedMessage);
@@ -155,7 +173,7 @@ public class EncryptedMessage {
}
private byte[] getMessageBody(byte[] message) {
- byte[] body = new byte[message.length - HEADER_LENGTH];
+ byte[] body = new byte[message.length - HEADER_LENGTH - MessageMac.MAC_LENGTH];
System.arraycopy(message, TEXT_OFFSET, body, 0, body.length);
return body;
diff --git a/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyBundleMessage.java b/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyBundleMessage.java
new file mode 100644
index 0000000000..e59091f3bd
--- /dev/null
+++ b/library/src/org/whispersystems/textsecure/crypto/protocol/PreKeyBundleMessage.java
@@ -0,0 +1,125 @@
+/**
+ * Copyright (C) 2013 Open Whisper Systems
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.whispersystems.textsecure.crypto.protocol;
+
+import org.whispersystems.textsecure.crypto.IdentityKey;
+import org.whispersystems.textsecure.crypto.InvalidKeyException;
+import org.whispersystems.textsecure.crypto.InvalidVersionException;
+import org.whispersystems.textsecure.crypto.PublicKey;
+import org.whispersystems.textsecure.util.Base64;
+import org.whispersystems.textsecure.util.Conversions;
+
+import java.io.IOException;
+
+/**
+ * Class responsible for parsing and constructing PreKeyBundle messages.
+ *
+ * The class takes an existing encrypted message and bundles in the necessary
+ * additional information for a prekeybundle, namely the addition of the local
+ * identity key.
+ */
+public class PreKeyBundleMessage {
+
+ private static final int VERSION_LENGTH = EncryptedMessage.VERSION_LENGTH;
+ private static final int IDENTITY_KEY_LENGTH = IdentityKey.SIZE;
+ public static final int HEADER_LENGTH = IDENTITY_KEY_LENGTH + EncryptedMessage.HEADER_LENGTH;
+
+ private static final int VERSION_OFFSET = EncryptedMessage.VERSION_OFFSET;
+ private static final int IDENTITY_KEY_OFFSET = VERSION_OFFSET + EncryptedMessage.VERSION_LENGTH;
+ private static final int PUBLIC_KEY_OFFSET = IDENTITY_KEY_LENGTH + EncryptedMessage.NEXT_KEY_OFFSET;
+ private static final int PREKEY_ID_OFFSET = IDENTITY_KEY_LENGTH + EncryptedMessage.RECEIVER_KEY_ID_OFFSET;
+
+ private final byte[] messageBytes;
+
+ private final int supportedVersion;
+ private final int messageVersion;
+ private final int preKeyId;
+ private final IdentityKey identityKey;
+ private final PublicKey publicKey;
+ private final byte[] bundledMessage;
+
+ public PreKeyBundleMessage(String message) throws InvalidKeyException, InvalidVersionException {
+ try {
+ this.messageBytes = Base64.decodeWithoutPadding(message);
+ this.messageVersion = Conversions.highBitsToInt(this.messageBytes[VERSION_OFFSET]);
+
+ if (messageVersion > EncryptedMessage.SUPPORTED_VERSION)
+ throw new InvalidVersionException("Key exchange with version: " + messageVersion +
+ " but we only support: " + EncryptedMessage.SUPPORTED_VERSION);
+
+ 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.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);
+ } catch (IOException e) {
+ throw new InvalidKeyException(e);
+ }
+ }
+
+ public PreKeyBundleMessage(IdentityKey identityKey, byte[] bundledMessage) {
+ try {
+ this.supportedVersion = EncryptedMessage.SUPPORTED_VERSION;
+ this.messageVersion = EncryptedMessage.SUPPORTED_VERSION;
+ this.identityKey = identityKey;
+ this.publicKey = new PublicKey(bundledMessage, EncryptedMessage.NEXT_KEY_OFFSET);
+ this.preKeyId = Conversions.byteArrayToMedium(bundledMessage, EncryptedMessage.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 String serialize() {
+ return Base64.encodeBytesWithoutPadding(this.messageBytes);
+ }
+
+ public int getSupportedVersion() {
+ return supportedVersion;
+ }
+
+ public int getMessageVersion() {
+ return messageVersion;
+ }
+
+ public IdentityKey getIdentityKey() {
+ return identityKey;
+ }
+
+ public PublicKey getPublicKey() {
+ return publicKey;
+ }
+
+ public String getBundledMessage() {
+ return Base64.encodeBytesWithoutPadding(bundledMessage);
+ }
+
+ public int getPreKeyId() {
+ return preKeyId;
+ }
+}
diff --git a/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java b/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java
index a10f5cd0d7..8a146a68bc 100644
--- a/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java
+++ b/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java
@@ -20,6 +20,7 @@ public class IncomingPushMessage implements Parcelable {
}
};
+ private int type;
private String source;
private List destinations;
private String messageText;
@@ -27,8 +28,9 @@ public class IncomingPushMessage implements Parcelable {
private long timestamp;
public IncomingPushMessage(String source, List destinations, String messageText,
- List attachments, long timestamp)
+ int type, List attachments, long timestamp)
{
+ this.type = type;
this.source = source;
this.destinations = destinations;
this.messageText = messageText;
@@ -84,4 +86,8 @@ public class IncomingPushMessage implements Parcelable {
dest.writeList(attachments);
dest.writeLong(timestamp);
}
+
+ public int getType() {
+ return type;
+ }
}
diff --git a/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java b/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java
index 7d658b96d5..fa0f7a6cc4 100644
--- a/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java
+++ b/library/src/org/whispersystems/textsecure/push/OutgoingPushMessage.java
@@ -5,29 +5,36 @@ import java.util.List;
public class OutgoingPushMessage {
+ public static final int TYPE_MESSAGE = 1;
+ public static final int TYPE_PREKEYED_MESSAGE = 2;
+
+ private int type;
private List destinations;
private String messageText;
private List attachments;
- public OutgoingPushMessage(String destination, String messageText) {
+ public OutgoingPushMessage(String destination, String messageText, int type) {
this.destinations = new LinkedList();
this.attachments = new LinkedList();
this.messageText = messageText;
this.destinations.add(destination);
+ this.type = type;
}
- public OutgoingPushMessage(List destinations, String messageText) {
+ public OutgoingPushMessage(List destinations, String messageText, int type) {
this.destinations = destinations;
this.messageText = messageText;
this.attachments = new LinkedList();
+ this.type = type;
}
public OutgoingPushMessage(List destinations, String messageText,
- List attachments)
+ List attachments, int type)
{
this.destinations = destinations;
this.messageText = messageText;
this.attachments = attachments;
+ this.type = type;
}
public List getDestinations() {
@@ -42,4 +49,7 @@ public class OutgoingPushMessage {
return attachments;
}
+ public int getType() {
+ return type;
+ }
}
diff --git a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java
index 16eb40d8d8..e55ef5c6d5 100644
--- a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java
+++ b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java
@@ -78,26 +78,26 @@ public class PushServiceSocket {
makeRequest(REGISTER_GCM_PATH, "DELETE", null);
}
- public void sendMessage(String recipient, String messageText)
+ public void sendMessage(String recipient, String messageText, int type)
throws IOException
{
- OutgoingPushMessage message = new OutgoingPushMessage(recipient, messageText);
+ OutgoingPushMessage message = new OutgoingPushMessage(recipient, messageText, type);
sendMessage(message);
}
- public void sendMessage(List recipients, String messageText)
+ public void sendMessage(List recipients, String messageText, int type)
throws IOException
{
- OutgoingPushMessage message = new OutgoingPushMessage(recipients, messageText);
+ OutgoingPushMessage message = new OutgoingPushMessage(recipients, messageText, type);
sendMessage(message);
}
public void sendMessage(List recipients, String messageText,
- List attachments)
+ List attachments, int type)
throws IOException
{
List attachmentIds = sendAttachments(attachments);
- OutgoingPushMessage message = new OutgoingPushMessage(recipients, messageText, attachmentIds);
+ OutgoingPushMessage message = new OutgoingPushMessage(recipients, messageText, attachmentIds, type);
sendMessage(message);
}
diff --git a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java
index 1da034570e..0e38236035 100644
--- a/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java
+++ b/src/org/thoughtcrime/securesms/ReceiveKeyActivity.java
@@ -31,7 +31,7 @@ import android.widget.Button;
import android.widget.TextView;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
-import org.thoughtcrime.securesms.crypto.InvalidVersionException;
+import org.whispersystems.textsecure.crypto.InvalidVersionException;
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
import org.whispersystems.textsecure.crypto.MasterSecret;
diff --git a/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java b/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java
index 0fe5fb924a..dd086efe3d 100644
--- a/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java
+++ b/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java
@@ -194,7 +194,9 @@ public class RegistrationProgressActivity extends SherlockActivity {
intent.putExtra("master_secret", masterSecret);
startService(intent);
} else {
- startActivity(new Intent(this, RegistrationActivity.class));
+ Intent intent = new Intent(this, RegistrationActivity.class);
+ intent.putExtra("master_secret", masterSecret);
+ startActivity(intent);
finish();
}
}
@@ -408,6 +410,7 @@ public class RegistrationProgressActivity extends SherlockActivity {
shutdownService();
Intent activityIntent = new Intent(RegistrationProgressActivity.this, RegistrationActivity.class);
+ activityIntent.putExtra("master_secret", masterSecret);
startActivity(activityIntent);
finish();
}
diff --git a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java
index 8f50d0c799..738eced2c8 100644
--- a/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java
+++ b/src/org/thoughtcrime/securesms/crypto/DecryptingQueue.java
@@ -34,8 +34,11 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
+import org.whispersystems.textsecure.crypto.IdentityKey;
+import org.whispersystems.textsecure.crypto.IdentityKeyPair;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.InvalidMessageException;
+import org.whispersystems.textsecure.crypto.InvalidVersionException;
import org.whispersystems.textsecure.crypto.SessionCipher;
import org.whispersystems.textsecure.crypto.protocol.EncryptedMessage;
import org.whispersystems.textsecure.util.Hex;
@@ -195,7 +198,8 @@ public class DecryptingQueue {
synchronized (SessionCipher.CIPHER_LOCK) {
Log.w("DecryptingQueue", "Decrypting: " + Hex.toString(ciphertextPduBytes));
- EncryptedMessage message = new EncryptedMessage(context, masterSecret, new TextTransport());
+ IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
+ EncryptedMessage message = new EncryptedMessage(context, masterSecret, identityKey, new TextTransport());
try {
plaintextPduBytes = message.decrypt(recipient, ciphertextPduBytes);
@@ -275,7 +279,9 @@ public class DecryptingQueue {
return;
}
- EncryptedMessage message = new EncryptedMessage(context, masterSecret, new SmsTransportDetails());
+ IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
+ EncryptedMessage message = new EncryptedMessage(context, masterSecret, identityKey, new SmsTransportDetails());
+
plaintextBody = new String(message.decrypt(recipient, body.getBytes()));
} catch (InvalidMessageException e) {
Log.w("DecryptionQueue", e);
diff --git a/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java b/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java
index 2c2ebde48c..e852539ab6 100644
--- a/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java
+++ b/src/org/thoughtcrime/securesms/crypto/IdentityKeyUtil.java
@@ -31,6 +31,7 @@ import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.crypto.signers.ECDSASigner;
import org.whispersystems.textsecure.crypto.IdentityKey;
+import org.whispersystems.textsecure.crypto.IdentityKeyPair;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.KeyUtil;
import org.whispersystems.textsecure.crypto.MasterCipher;
@@ -75,6 +76,22 @@ public class IdentityKeyUtil {
return null;
}
}
+
+ public static IdentityKeyPair getIdentityKeyPair(Context context, MasterSecret masterSecret) {
+ if (!hasIdentityKey(context))
+ return null;
+
+ try {
+ MasterCipher masterCipher = new MasterCipher(masterSecret);
+ IdentityKey publicKey = getIdentityKey(context);
+ byte[] privateKeyBytes = Base64.decode(retrieve(context, IDENTITY_PRIVATE_KEY_PREF));
+ ECPrivateKeyParameters privateKey = masterCipher.decryptKey(privateKeyBytes);
+
+ return new IdentityKeyPair(publicKey, privateKey);
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ }
public static String getFingerprint(Context context) {
if (!hasIdentityKey(context)) return null;
diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java
index 7c02a67fa4..9352712bfc 100644
--- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java
+++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java
@@ -23,12 +23,16 @@ import android.util.Log;
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.whispersystems.textsecure.crypto.IdentityKey;
+import org.whispersystems.textsecure.crypto.KeyPair;
import org.whispersystems.textsecure.crypto.KeyUtil;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.PublicKey;
+import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
import org.whispersystems.textsecure.crypto.protocol.EncryptedMessage;
import org.whispersystems.textsecure.push.PreKeyEntity;
+import org.whispersystems.textsecure.storage.InvalidKeyIdException;
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;
@@ -72,6 +76,10 @@ public class KeyExchangeProcessor {
return isTrusted(message.getIdentityKey());
}
+ public boolean isTrusted(PreKeyBundleMessage message) {
+ return isTrusted(message.getIdentityKey());
+ }
+
public boolean isTrusted(IdentityKey identityKey) {
return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret, recipient,
identityKey);
@@ -94,6 +102,40 @@ public class KeyExchangeProcessor {
(localKeyRecord.getCurrentKeyPair() != null && localKeyRecord.getCurrentKeyPair().getId() != responseKeyId);
}
+ public void processKeyExchangeMessage(PreKeyBundleMessage message) throws InvalidKeyIdException {
+ int preKeyId = message.getPreKeyId();
+ PublicKey remoteKey = message.getPublicKey();
+ IdentityKey remoteIdentity = message.getIdentityKey();
+
+ Log.w("KeyExchangeProcessor", "Received pre-key with remote key ID: " + remoteKey.getId());
+ Log.w("KeyExchangeProcessor", "Received pre-key with local key ID: " + preKeyId);
+
+ if (!PreKeyRecord.hasRecord(context, preKeyId))
+ throw new InvalidKeyIdException("No such prekey: " + preKeyId);
+
+ PreKeyRecord preKeyRecord = new PreKeyRecord(context, masterSecret, preKeyId);
+ KeyPair preKeyPair = new KeyPair(preKeyId, preKeyRecord.getKeyPair().getKeyPair(), masterSecret);
+
+ localKeyRecord.setCurrentKeyPair(preKeyPair);
+ localKeyRecord.setNextKeyPair(preKeyPair);
+
+ remoteKeyRecord.setCurrentRemoteKey(remoteKey);
+ remoteKeyRecord.setLastRemoteKey(remoteKey);
+
+ sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(),
+ remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
+ sessionRecord.setIdentityKey(remoteIdentity);
+ sessionRecord.setSessionVersion(Math.min(message.getSupportedVersion(), EncryptedMessage.SUPPORTED_VERSION));
+
+
+ localKeyRecord.save();
+ remoteKeyRecord.save();
+ sessionRecord.save();
+
+ DatabaseFactory.getIdentityDatabase(context)
+ .saveIdentity(masterSecret, recipient, remoteIdentity);
+ }
+
public void processKeyExchangeMessage(PreKeyEntity message) {
PublicKey remoteKey = new PublicKey(message.getKeyId(), message.getPublicKey());
remoteKeyRecord.setCurrentRemoteKey(remoteKey);
@@ -101,6 +143,8 @@ public class KeyExchangeProcessor {
remoteKeyRecord.save();
localKeyRecord = KeyUtil.initializeRecordFor(recipient, context, masterSecret);
+ localKeyRecord.setNextKeyPair(localKeyRecord.getCurrentKeyPair());
+ localKeyRecord.save();
sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(),
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
diff --git a/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java b/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java
index a38bde8c3b..6cf784020e 100644
--- a/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java
+++ b/src/org/thoughtcrime/securesms/crypto/protocol/KeyExchangeMessage.java
@@ -20,7 +20,7 @@ import android.content.Context;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
-import org.thoughtcrime.securesms.crypto.InvalidVersionException;
+import org.whispersystems.textsecure.crypto.InvalidVersionException;
import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.MasterSecret;
@@ -96,7 +96,7 @@ public class KeyExchangeMessage {
if (messageVersion > EncryptedMessage.SUPPORTED_VERSION)
throw new InvalidVersionException("Key exchange with version: " + messageVersion +
" but we only support: " + EncryptedMessage.SUPPORTED_VERSION);
-
+
if (messageVersion >= 1)
keyBytes = Base64.decodeWithoutPadding(messageBody);
diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java
index e7d6000017..0579d1dd4e 100644
--- a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java
+++ b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java
@@ -27,9 +27,11 @@ public interface MmsSmsColumns {
BASE_SENDING_TYPE, BASE_SENT_FAILED_TYPE};
// Key Exchange Information
- protected static final long KEY_EXCHANGE_BIT = 0x8000;
- protected static final long KEY_EXCHANGE_STALE_BIT = 0x4000;
- protected static final long KEY_EXCHANGE_PROCESSED_BIT = 0x2000;
+ protected static final long KEY_EXCHANGE_BIT = 0x8000;
+ protected static final long KEY_EXCHANGE_STALE_BIT = 0x4000;
+ protected static final long KEY_EXCHANGE_PROCESSED_BIT = 0x2000;
+ protected static final long KEY_EXCHANGE_CORRUPTED_BIT = 0x1000;
+ protected static final long KEY_EXCHANGE_INVALID_VERSION_BIT = 0x800;
// Secure Message Information
protected static final long SECURE_MESSAGE_BIT = 0x800000;
@@ -81,6 +83,14 @@ public interface MmsSmsColumns {
return (type & KEY_EXCHANGE_PROCESSED_BIT) != 0;
}
+ public static boolean isCorruptedKeyExchange(long type) {
+ return (type & KEY_EXCHANGE_CORRUPTED_BIT) != 0;
+ }
+
+ public static boolean isInvalidVersionKeyExchange(long type) {
+ return (type & KEY_EXCHANGE_INVALID_VERSION_BIT) != 0;
+ }
+
public static boolean isSymmetricEncryption(long type) {
return (type & ENCRYPTION_SYMMETRIC_BIT) != 0;
}
diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java
index 56e2a6298e..703d43ac5b 100644
--- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java
@@ -235,8 +235,10 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
protected Pair insertMessageInbox(IncomingTextMessage message, long type) {
if (message.isKeyExchange()) {
type |= Types.KEY_EXCHANGE_BIT;
- if (((IncomingKeyExchangeMessage)message).isStale()) type |= Types.KEY_EXCHANGE_STALE_BIT;
- else if (((IncomingKeyExchangeMessage)message).isProcessed()) type |= Types.KEY_EXCHANGE_PROCESSED_BIT;
+ if (((IncomingKeyExchangeMessage)message).isStale()) type |= Types.KEY_EXCHANGE_STALE_BIT;
+ else if (((IncomingKeyExchangeMessage)message).isProcessed()) type |= Types.KEY_EXCHANGE_PROCESSED_BIT;
+ else if (((IncomingKeyExchangeMessage)message).isCorrupted()) type |= Types.KEY_EXCHANGE_CORRUPTED_BIT;
+ else if (((IncomingKeyExchangeMessage)message).isInvalidVersion()) type |= Types.KEY_EXCHANGE_INVALID_VERSION_BIT;
} else if (message.isSecureMessage()) {
type |= Types.SECURE_MESSAGE_BIT;
type |= Types.ENCRYPTION_REMOTE_BIT;
diff --git a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java
index 2f40fe8524..ec43b0843e 100644
--- a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java
+++ b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java
@@ -103,6 +103,14 @@ public abstract class MessageRecord extends DisplayRecord {
return SmsDatabase.Types.isProcessedKeyExchange(type);
}
+ public boolean isCorruptedKeyExchange() {
+ return SmsDatabase.Types.isCorruptedKeyExchange(type);
+ }
+
+ public boolean isInvalidVersionKeyExchange() {
+ return SmsDatabase.Types.isInvalidVersionKeyExchange(type);
+ }
+
public Recipient getIndividualRecipient() {
return individualRecipient;
}
diff --git a/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java b/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java
index 33f55e7b70..f14cd95848 100644
--- a/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java
+++ b/src/org/thoughtcrime/securesms/gcm/GcmIntentService.java
@@ -12,7 +12,6 @@ import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.push.PushServiceSocket;
-import org.whispersystems.textsecure.push.RateLimitException;
import org.whispersystems.textsecure.util.Util;
import java.io.IOException;
@@ -73,6 +72,7 @@ public class GcmIntentService extends GCMBaseIntentService {
Intent receivedIntent = new Intent(context, SendReceiveService.class);
receivedIntent.setAction(SendReceiveService.RECEIVE_SMS_ACTION);
receivedIntent.putParcelableArrayListExtra("text_messages", messages);
+ receivedIntent.putExtra("push_type", message.getType());
context.startService(receivedIntent);
}
diff --git a/src/org/thoughtcrime/securesms/protocol/WirePrefix.java b/src/org/thoughtcrime/securesms/protocol/WirePrefix.java
index 7b4214d358..072232ff4e 100644
--- a/src/org/thoughtcrime/securesms/protocol/WirePrefix.java
+++ b/src/org/thoughtcrime/securesms/protocol/WirePrefix.java
@@ -47,6 +47,10 @@ public abstract class WirePrefix {
return verifyPrefix("?TSM", message);
}
+ public static boolean isPreKeyBundle(String message) {
+ return verifyPrefix("?TSP", message);
+ }
+
public static String calculateKeyExchangePrefix(String message) {
return calculatePrefix(("?TSK" + message).getBytes(), PREFIX_BYTES);
}
@@ -55,6 +59,10 @@ public abstract class WirePrefix {
return calculatePrefix(("?TSM" + message).getBytes(), PREFIX_BYTES);
}
+ public static String calculatePreKeyBundlePrefix(String message) {
+ return calculatePrefix(("?TSP" + message).getBytes(), PREFIX_BYTES);
+ }
+
private static boolean verifyPrefix(String prefixType, String message) {
if (message.length() <= PREFIX_SIZE)
return false;
diff --git a/src/org/thoughtcrime/securesms/service/SmsReceiver.java b/src/org/thoughtcrime/securesms/service/SmsReceiver.java
index efe8a7604b..11f0dd0d5c 100644
--- a/src/org/thoughtcrime/securesms/service/SmsReceiver.java
+++ b/src/org/thoughtcrime/securesms/service/SmsReceiver.java
@@ -22,8 +22,11 @@ import android.util.Log;
import android.util.Pair;
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
+import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
+import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
+import org.thoughtcrime.securesms.transport.PushTransport;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
-import org.thoughtcrime.securesms.crypto.InvalidVersionException;
+import org.whispersystems.textsecure.crypto.InvalidVersionException;
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
import org.whispersystems.textsecure.crypto.MasterSecret;
@@ -38,6 +41,8 @@ import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
+import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
+import org.whispersystems.textsecure.storage.InvalidKeyIdException;
import java.util.List;
@@ -48,15 +53,32 @@ public class SmsReceiver {
private final Context context;
public SmsReceiver(Context context) {
- this.context = context;
+ this.context = context;
}
+ private IncomingTextMessage assembleMessageFragments(List messages, int pushType) {
+ if (messages.size() != 1) return assembleMessageFragments(messages);
+
+ IncomingTextMessage message = messages.get(0);
+
+ switch (pushType) {
+ case PushTransport.TYPE_MESSAGE_CIPHERTEXT:
+ return new IncomingEncryptedMessage(message, message.getMessageBody());
+ case PushTransport.TYPE_MESSAGE_PREKEY_BUNDLE:
+ return new IncomingPreKeyBundleMessage(message, message.getMessageBody());
+ case PushTransport.TYPE_MESSAGE_KEY_EXCHANGE:
+ return new IncomingKeyExchangeMessage(message, message.getMessageBody());
+ }
+
+ return message;
+ }
private IncomingTextMessage assembleMessageFragments(List messages) {
IncomingTextMessage message = new IncomingTextMessage(messages);
if (WirePrefix.isEncryptedMessage(message.getMessageBody()) ||
- WirePrefix.isKeyExchange(message.getMessageBody()))
+ WirePrefix.isKeyExchange(message.getMessageBody()) ||
+ WirePrefix.isPreKeyBundle(message.getMessageBody()))
{
return multipartMessageHandler.processPotentialMultipartMessage(message);
} else {
@@ -91,6 +113,43 @@ public class SmsReceiver {
}
}
+ private Pair storePreKeyBundledMessage(MasterSecret masterSecret,
+ IncomingKeyExchangeMessage message)
+ {
+ Log.w("SmsReceiver", "Processing prekey message...");
+
+ try {
+ Recipient recipient = new Recipient(null, message.getSender(), null, null);
+ KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient);
+ PreKeyBundleMessage preKeyExchange = new PreKeyBundleMessage(message.getMessageBody());
+
+ if (processor.isTrusted(preKeyExchange)) {
+ processor.processKeyExchangeMessage(preKeyExchange);
+
+ IncomingEncryptedMessage bundledMessage = new IncomingEncryptedMessage(message, preKeyExchange.getBundledMessage());
+ Pair messageAndThreadId = storeSecureMessage(masterSecret, bundledMessage);
+
+ Intent intent = new Intent(KeyExchangeProcessor.SECURITY_UPDATE_EVENT);
+ intent.putExtra("thread_id", messageAndThreadId.second);
+ intent.setPackage(context.getPackageName());
+ context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION);
+
+ return messageAndThreadId;
+ }
+ } catch (InvalidKeyException e) {
+ Log.w("SmsReceiver", e);
+ message.setCorrupted(true);
+ } catch (InvalidVersionException e) {
+ Log.w("SmsReceiver", e);
+ message.setInvalidVersion(true);
+ } catch (InvalidKeyIdException e) {
+ Log.w("SmsReceiver", e);
+ message.setStale(true);
+ }
+
+ return storeStandardMessage(masterSecret, message);
+ }
+
private Pair storeKeyExchangeMessage(MasterSecret masterSecret,
IncomingKeyExchangeMessage message)
{
@@ -114,8 +173,10 @@ public class SmsReceiver {
}
} catch (InvalidVersionException e) {
Log.w("SmsReceiver", e);
+ message.setInvalidVersion(true);
} catch (InvalidKeyException e) {
Log.w("SmsReceiver", e);
+ message.setCorrupted(true);
}
}
@@ -124,13 +185,19 @@ public class SmsReceiver {
private Pair storeMessage(MasterSecret masterSecret, IncomingTextMessage message) {
if (message.isSecureMessage()) return storeSecureMessage(masterSecret, message);
- else if (message.isKeyExchange()) return storeKeyExchangeMessage(masterSecret, (IncomingKeyExchangeMessage)message);
+ else if (message.isPreKeyBundle()) return storePreKeyBundledMessage(masterSecret, (IncomingKeyExchangeMessage) message);
+ else if (message.isKeyExchange()) return storeKeyExchangeMessage(masterSecret, (IncomingKeyExchangeMessage) message);
else return storeStandardMessage(masterSecret, message);
}
private void handleReceiveMessage(MasterSecret masterSecret, Intent intent) {
List messagesList = intent.getExtras().getParcelableArrayList("text_messages");
- IncomingTextMessage message = assembleMessageFragments(messagesList);
+ int pushType = intent.getIntExtra("push_type", -1);
+
+ IncomingTextMessage message;
+
+ if (pushType != -1) message = assembleMessageFragments(messagesList, pushType);
+ else message = assembleMessageFragments(messagesList);
if (message != null) {
Pair messageAndThreadId = storeMessage(masterSecret, message);
diff --git a/src/org/thoughtcrime/securesms/sms/IncomingEncryptedMessage.java b/src/org/thoughtcrime/securesms/sms/IncomingEncryptedMessage.java
index 8df3ba7d42..63489bf7bc 100644
--- a/src/org/thoughtcrime/securesms/sms/IncomingEncryptedMessage.java
+++ b/src/org/thoughtcrime/securesms/sms/IncomingEncryptedMessage.java
@@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.sms;
public class IncomingEncryptedMessage extends IncomingTextMessage {
- IncomingEncryptedMessage(IncomingTextMessage base, String newBody) {
+ public IncomingEncryptedMessage(IncomingTextMessage base, String newBody) {
super(base, newBody);
}
diff --git a/src/org/thoughtcrime/securesms/sms/IncomingKeyExchangeMessage.java b/src/org/thoughtcrime/securesms/sms/IncomingKeyExchangeMessage.java
index 59bfe897ab..56020708bf 100644
--- a/src/org/thoughtcrime/securesms/sms/IncomingKeyExchangeMessage.java
+++ b/src/org/thoughtcrime/securesms/sms/IncomingKeyExchangeMessage.java
@@ -4,13 +4,17 @@ public class IncomingKeyExchangeMessage extends IncomingTextMessage {
private boolean isStale;
private boolean isProcessed;
+ private boolean isCorrupted;
+ private boolean isInvalidVersion;
- IncomingKeyExchangeMessage(IncomingTextMessage base, String newBody) {
+ public IncomingKeyExchangeMessage(IncomingTextMessage base, String newBody) {
super(base, newBody);
if (base instanceof IncomingKeyExchangeMessage) {
- this.isStale = ((IncomingKeyExchangeMessage)base).isStale;
- this.isProcessed = ((IncomingKeyExchangeMessage)base).isProcessed;
+ this.isStale = ((IncomingKeyExchangeMessage)base).isStale;
+ this.isProcessed = ((IncomingKeyExchangeMessage)base).isProcessed;
+ this.isCorrupted = ((IncomingKeyExchangeMessage)base).isCorrupted;
+ this.isInvalidVersion = ((IncomingKeyExchangeMessage)base).isInvalidVersion;
}
}
@@ -35,6 +39,22 @@ public class IncomingKeyExchangeMessage extends IncomingTextMessage {
this.isProcessed = isProcessed;
}
+ public boolean isCorrupted() {
+ return isCorrupted;
+ }
+
+ public void setCorrupted(boolean isCorrupted) {
+ this.isCorrupted = isCorrupted;
+ }
+
+ public boolean isInvalidVersion() {
+ return isInvalidVersion;
+ }
+
+ public void setInvalidVersion(boolean isInvalidVersion) {
+ this.isInvalidVersion = isInvalidVersion;
+ }
+
@Override
public boolean isKeyExchange() {
return true;
diff --git a/src/org/thoughtcrime/securesms/sms/IncomingPreKeyBundleMessage.java b/src/org/thoughtcrime/securesms/sms/IncomingPreKeyBundleMessage.java
new file mode 100644
index 0000000000..c4103564b0
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/sms/IncomingPreKeyBundleMessage.java
@@ -0,0 +1,20 @@
+package org.thoughtcrime.securesms.sms;
+
+import org.whispersystems.textsecure.push.IncomingPushMessage;
+
+public class IncomingPreKeyBundleMessage extends IncomingKeyExchangeMessage {
+
+ public IncomingPreKeyBundleMessage(IncomingTextMessage base, String newBody) {
+ super(base, newBody);
+ }
+
+ @Override
+ public IncomingPreKeyBundleMessage withMessageBody(String messageBody) {
+ return new IncomingPreKeyBundleMessage(this, messageBody);
+ }
+
+ @Override
+ public boolean isPreKeyBundle() {
+ return true;
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java b/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java
index 4b38ce93ac..fba40c865d 100644
--- a/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java
+++ b/src/org/thoughtcrime/securesms/sms/IncomingTextMessage.java
@@ -126,6 +126,10 @@ public class IncomingTextMessage implements Parcelable {
return false;
}
+ public boolean isPreKeyBundle() {
+ return false;
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/src/org/thoughtcrime/securesms/sms/RawTransportDetails.java b/src/org/thoughtcrime/securesms/sms/RawTransportDetails.java
new file mode 100644
index 0000000000..17b0fe372a
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/sms/RawTransportDetails.java
@@ -0,0 +1,27 @@
+package org.thoughtcrime.securesms.sms;
+
+import org.whispersystems.textsecure.crypto.TransportDetails;
+
+import java.io.IOException;
+
+public class RawTransportDetails implements TransportDetails {
+ @Override
+ public byte[] getStrippedPaddingMessageBody(byte[] messageWithPadding) {
+ return messageWithPadding;
+ }
+
+ @Override
+ public byte[] getPaddedMessageBody(byte[] messageBody) {
+ return messageBody;
+ }
+
+ @Override
+ public byte[] getEncodedMessage(byte[] messageWithMac) {
+ return messageWithMac;
+ }
+
+ @Override
+ public byte[] getDecodedMessage(byte[] encodedMessageBytes) throws IOException {
+ return encodedMessageBytes;
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/transport/MmsTransport.java b/src/org/thoughtcrime/securesms/transport/MmsTransport.java
index 2331c527ab..fd7790e383 100644
--- a/src/org/thoughtcrime/securesms/transport/MmsTransport.java
+++ b/src/org/thoughtcrime/securesms/transport/MmsTransport.java
@@ -5,6 +5,9 @@ import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.Pair;
+import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
+import org.whispersystems.textsecure.crypto.IdentityKey;
+import org.whispersystems.textsecure.crypto.IdentityKeyPair;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipher;
import org.thoughtcrime.securesms.database.MmsDatabase;
@@ -134,7 +137,8 @@ public class MmsTransport {
}
private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipient, byte[] pduBytes) {
- EncryptedMessage message = new EncryptedMessage(context, masterSecret, new TextTransport());
+ IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
+ EncryptedMessage message = new EncryptedMessage(context, masterSecret, identityKey, new TextTransport());
return message.encrypt(new Recipient(null, recipient, null, null), pduBytes);
}
diff --git a/src/org/thoughtcrime/securesms/transport/PushTransport.java b/src/org/thoughtcrime/securesms/transport/PushTransport.java
index 16972baa3c..9c27c39689 100644
--- a/src/org/thoughtcrime/securesms/transport/PushTransport.java
+++ b/src/org/thoughtcrime/securesms/transport/PushTransport.java
@@ -3,16 +3,20 @@ package org.thoughtcrime.securesms.transport;
import android.content.Context;
import android.util.Log;
+import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
-import org.thoughtcrime.securesms.mms.TextTransport;
-import org.thoughtcrime.securesms.recipients.Recipient;
-import org.thoughtcrime.securesms.sms.SmsTransportDetails;
-import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.mms.PartParser;
+import org.thoughtcrime.securesms.mms.TextTransport;
+import org.thoughtcrime.securesms.recipients.Recipient;
+import org.thoughtcrime.securesms.sms.RawTransportDetails;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
-import org.whispersystems.textsecure.crypto.SessionCipher;
+import org.whispersystems.textsecure.crypto.IdentityKey;
+import org.whispersystems.textsecure.crypto.IdentityKeyPair;
+import org.whispersystems.textsecure.crypto.InvalidKeyException;
+import org.whispersystems.textsecure.crypto.MasterSecret;
+import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
import org.whispersystems.textsecure.crypto.protocol.EncryptedMessage;
import org.whispersystems.textsecure.push.PreKeyEntity;
import org.whispersystems.textsecure.push.PushAttachmentData;
@@ -31,6 +35,11 @@ import ws.com.google.android.mms.pdu.SendReq;
public class PushTransport extends BaseTransport {
+ public static final int TYPE_MESSAGE_PLAINTEXT = 0;
+ public static final int TYPE_MESSAGE_CIPHERTEXT = 1;
+ public static final int TYPE_MESSAGE_KEY_EXCHANGE = 2;
+ public static final int TYPE_MESSAGE_PREKEY_BUNDLE = 3;
+
private final Context context;
private final MasterSecret masterSecret;
@@ -50,18 +59,15 @@ public class PushTransport extends BaseTransport {
String recipientCanonicalNumber = PhoneNumberFormatter.formatNumber(recipient.getNumber(),
localNumber);
-// if (SessionRecord.hasSession(context, recipient)) {
-// byte[] cipherText = getEncryptedMessageForExistingSession(recipient, plaintext);
-// socket.sendMessage(recipientCanonicalNumber, new String(cipherText));
-// } else {
-// byte[] cipherText = getEncryptedMessageForNewSession(socket, recipient,
-// recipientCanonicalNumber,
-// plaintext);
-// socket.sendMessage(recipientCanonicalNumber, new String(cipherText));
-// }
-
-
- socket.sendMessage(recipientCanonicalNumber, message.getBody().getBody());
+ if (SessionRecord.hasSession(context, recipient)) {
+ byte[] cipherText = getEncryptedMessageForExistingSession(recipient, plaintext);
+ socket.sendMessage(recipientCanonicalNumber, new String(cipherText), TYPE_MESSAGE_CIPHERTEXT);
+ } else {
+ byte[] cipherText = getEncryptedMessageForNewSession(socket, recipient,
+ recipientCanonicalNumber,
+ plaintext);
+ socket.sendMessage(recipientCanonicalNumber, new String(cipherText), TYPE_MESSAGE_PREKEY_BUNDLE);
+ }
context.sendBroadcast(constructSentIntent(context, message.getId(), message.getType()));
} catch (RateLimitException e) {
@@ -78,8 +84,8 @@ public class PushTransport extends BaseTransport {
String messageText = PartParser.getMessageText(message.getBody());
List attachments = getAttachmentsFromBody(message.getBody());
- if (attachments.isEmpty()) socket.sendMessage(destinations, messageText);
- else socket.sendMessage(destinations, messageText, attachments);
+ if (attachments.isEmpty()) socket.sendMessage(destinations, messageText, TYPE_MESSAGE_PLAINTEXT);
+ else socket.sendMessage(destinations, messageText, attachments, TYPE_MESSAGE_PLAINTEXT);
} catch (RateLimitException e) {
Log.w("PushTransport", e);
throw new IOException("Rate limit exceeded.");
@@ -103,20 +109,29 @@ public class PushTransport extends BaseTransport {
return attachments;
}
- private byte[] getEncryptedMessageForNewSession(PushServiceSocket socket, Recipient recipient, String canonicalRecipientNumber, String plaintext) throws IOException {
- PreKeyEntity preKey = socket.getPreKey(canonicalRecipientNumber);
- KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient);
+ private byte[] getEncryptedMessageForNewSession(PushServiceSocket socket, Recipient recipient,
+ String canonicalRecipientNumber, String plaintext)
+ throws IOException
+ {
+ IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
+ IdentityKey identityKey = identityKeyPair.getPublicKey();
+ PreKeyEntity preKey = socket.getPreKey(canonicalRecipientNumber);
+ KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient);
+
processor.processKeyExchangeMessage(preKey);
- return plaintext.getBytes();
-// synchronized (SessionCipher.CIPHER_LOCK) {
-// SessionCipher sessionCipher = new SessionCipher(context, masterSecret, recipient, new SmsTransportDetails());
-// return sessionCipher.encryptMessage(plaintext.getBytes());
-// }
+ EncryptedMessage message = new EncryptedMessage(context, masterSecret, identityKeyPair, new RawTransportDetails());
+ byte[] bundledMessage = message.encrypt(recipient, plaintext.getBytes());
+
+ PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(identityKey, bundledMessage);
+ return preKeyBundleMessage.serialize().getBytes();
}
- private byte[] getEncryptedMessageForExistingSession(Recipient recipient, String plaintext) {
- EncryptedMessage message = new EncryptedMessage(context, masterSecret, new TextTransport());
+ private byte[] getEncryptedMessageForExistingSession(Recipient recipient, String plaintext)
+ throws IOException
+ {
+ IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
+ EncryptedMessage message = new EncryptedMessage(context, masterSecret, identityKeyPair, new TextTransport());
return message.encrypt(recipient, plaintext.getBytes());
}
diff --git a/src/org/thoughtcrime/securesms/transport/SmsTransport.java b/src/org/thoughtcrime/securesms/transport/SmsTransport.java
index 1b000bf3ba..69e245adc1 100644
--- a/src/org/thoughtcrime/securesms/transport/SmsTransport.java
+++ b/src/org/thoughtcrime/securesms/transport/SmsTransport.java
@@ -5,6 +5,8 @@ import android.content.Context;
import android.telephony.SmsManager;
import android.util.Log;
+import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
+import org.whispersystems.textsecure.crypto.IdentityKeyPair;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipher;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
@@ -139,7 +141,8 @@ public class SmsTransport extends BaseTransport {
}
private String getAsymmetricEncrypt(MasterSecret masterSecret, String body, Recipient recipient) {
- EncryptedMessage message = new EncryptedMessage(context, masterSecret, new SmsTransportDetails());
+ IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
+ EncryptedMessage message = new EncryptedMessage(context, masterSecret, identityKey, new SmsTransportDetails());
return new String(message.encrypt(recipient, body.getBytes()));
}
}