diff --git a/library/src/org/whispersystems/textsecure/crypto/KeyUtil.java b/library/src/org/whispersystems/textsecure/crypto/KeyUtil.java
index 6fc722fdb8..a08c18d83a 100644
--- a/library/src/org/whispersystems/textsecure/crypto/KeyUtil.java
+++ b/library/src/org/whispersystems/textsecure/crypto/KeyUtil.java
@@ -107,6 +107,11 @@ public class KeyUtil {
(SessionRecord.hasSession(context, recipient));
}
+ public static boolean isNonPrekeySessionFor(Context context, MasterSecret masterSecret, CanonicalRecipientAddress recipient) {
+ return isSessionFor(context, recipient) &&
+ !(new SessionRecord(context, masterSecret, recipient).isPrekeyBundleRequired());
+ }
+
public static boolean isIdentityKeyFor(Context context,
MasterSecret masterSecret,
CanonicalRecipientAddress recipient)
diff --git a/library/src/org/whispersystems/textsecure/crypto/MessageCipher.java b/library/src/org/whispersystems/textsecure/crypto/MessageCipher.java
index 49fa4f57f0..6f45267773 100644
--- a/library/src/org/whispersystems/textsecure/crypto/MessageCipher.java
+++ b/library/src/org/whispersystems/textsecure/crypto/MessageCipher.java
@@ -36,17 +36,17 @@ public class MessageCipher {
public static final int SUPPORTED_VERSION = 2;
public static final int CRADLE_AGREEMENT_VERSION = 2;
- static final int VERSION_LENGTH = 1;
+ 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;
- static final int NEXT_KEY_LENGTH = PublicKey.KEY_SIZE;
+ 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;
- static final int VERSION_OFFSET = 0;
+ public static final int VERSION_OFFSET = 0;
private static final int SENDER_KEY_ID_OFFSET = VERSION_OFFSET + VERSION_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;
+ 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;
diff --git a/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java b/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java
index b4ba361013..11d0d36d77 100644
--- a/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java
+++ b/library/src/org/whispersystems/textsecure/crypto/SessionCipher.java
@@ -131,6 +131,7 @@ public class SessionCipher {
context.getSessionRecord().setSessionKey(context.getSessionKey());
context.getSessionRecord().setSessionVersion(context.getNegotiatedVersion());
+ context.getSessionRecord().setPrekeyBundleRequired(false);
context.getSessionRecord().save();
return plaintextWithPadding;
diff --git a/library/src/org/whispersystems/textsecure/push/PushTransportDetails.java b/library/src/org/whispersystems/textsecure/push/PushTransportDetails.java
new file mode 100644
index 0000000000..9920066f52
--- /dev/null
+++ b/library/src/org/whispersystems/textsecure/push/PushTransportDetails.java
@@ -0,0 +1,44 @@
+/**
+ * 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.push;
+
+import org.whispersystems.textsecure.crypto.TransportDetails;
+import org.whispersystems.textsecure.util.Base64;
+
+import java.io.IOException;
+
+public class PushTransportDetails 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 Base64.encodeBytesWithoutPadding(messageWithMac).getBytes();
+ }
+
+ @Override
+ public byte[] getDecodedMessage(byte[] encodedMessageBytes) throws IOException {
+ return Base64.decodeWithoutPadding(new String(encodedMessageBytes));
+ }
+}
diff --git a/library/src/org/whispersystems/textsecure/storage/SessionRecord.java b/library/src/org/whispersystems/textsecure/storage/SessionRecord.java
index 566ea42cc6..6ec97e626f 100644
--- a/library/src/org/whispersystems/textsecure/storage/SessionRecord.java
+++ b/library/src/org/whispersystems/textsecure/storage/SessionRecord.java
@@ -36,8 +36,9 @@ import java.nio.channels.FileChannel;
*/
public class SessionRecord extends Record {
- private static final int CURRENT_VERSION_MARKER = 0X55555556;
- private static final int[] VALID_VERSION_MARKERS = {CURRENT_VERSION_MARKER, 0X55555555};
+
+ private static final int CURRENT_VERSION_MARKER = 0X55555557;
+ private static final int[] VALID_VERSION_MARKERS = {CURRENT_VERSION_MARKER, 0X55555556, 0X55555555};
private static final Object FILE_LOCK = new Object();
private int counter;
@@ -48,6 +49,7 @@ public class SessionRecord extends Record {
private IdentityKey identityKey;
private SessionKey sessionKeyRecord;
private boolean verifiedSessionKey;
+ private boolean prekeyBundleRequired;
private final MasterSecret masterSecret;
@@ -63,7 +65,7 @@ public class SessionRecord extends Record {
}
public static void delete(Context context, CanonicalRecipientAddress recipient) {
- delete(context, SESSIONS_DIRECTORY, getRecipientId(context, recipient)+"");
+ delete(context, SESSIONS_DIRECTORY, getRecipientId(context, recipient) + "");
}
public static boolean hasSession(Context context, CanonicalRecipientAddress recipient) {
@@ -116,6 +118,14 @@ public class SessionRecord extends Record {
return this.identityKey;
}
+ public boolean isPrekeyBundleRequired() {
+ return prekeyBundleRequired;
+ }
+
+ public void setPrekeyBundleRequired(boolean prekeyBundleRequired) {
+ this.prekeyBundleRequired = prekeyBundleRequired;
+ }
+
// public void setVerifiedSessionKey(boolean verifiedSessionKey) {
// this.verifiedSessionKey = verifiedSessionKey;
// }
@@ -162,6 +172,7 @@ public class SessionRecord extends Record {
writeInteger(sessionVersion, out);
writeIdentityKey(out);
writeInteger(verifiedSessionKey ? 1 : 0, out);
+ writeInteger(prekeyBundleRequired ? 1 : 0, out);
if (sessionKeyRecord != null)
writeBlob(sessionKeyRecord.serialize(), out);
@@ -202,6 +213,10 @@ public class SessionRecord extends Record {
this.verifiedSessionKey = (readInteger(in) == 1);
}
+ if (versionMarker >= 0X55555557) {
+ this.prekeyBundleRequired = (readInteger(in) == 1);
+ }
+
if (in.available() != 0)
this.sessionKeyRecord = new SessionKey(readBlob(in), masterSecret);
@@ -226,4 +241,5 @@ public class SessionRecord extends Record {
return null;
}
+
}
diff --git a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java
index 69b32856db..8a452db32d 100644
--- a/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java
+++ b/src/org/thoughtcrime/securesms/crypto/KeyExchangeProcessor.java
@@ -110,6 +110,11 @@ public class KeyExchangeProcessor {
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) && KeyUtil.isSessionFor(context, recipient)) {
+ Log.w("KeyExchangeProcessor", "We've already processed the prekey part, letting bundled message fall through...");
+ return;
+ }
+
if (!PreKeyRecord.hasRecord(context, preKeyId))
throw new InvalidKeyIdException("No such prekey: " + preKeyId);
@@ -152,6 +157,7 @@ public class KeyExchangeProcessor {
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
sessionRecord.setIdentityKey(message.getIdentityKey());
sessionRecord.setSessionVersion(MessageCipher.SUPPORTED_VERSION);
+ sessionRecord.setPrekeyBundleRequired(true);
sessionRecord.save();
DatabaseFactory.getIdentityDatabase(context)
diff --git a/src/org/thoughtcrime/securesms/protocol/PrekeyBundleWirePrefix.java b/src/org/thoughtcrime/securesms/protocol/PrekeyBundleWirePrefix.java
new file mode 100644
index 0000000000..1596fa84fa
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/protocol/PrekeyBundleWirePrefix.java
@@ -0,0 +1,8 @@
+package org.thoughtcrime.securesms.protocol;
+
+public class PrekeyBundleWirePrefix extends WirePrefix {
+ @Override
+ public String calculatePrefix(String message) {
+ return super.calculatePreKeyBundlePrefix(message);
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/sms/MultipartSmsMessageHandler.java b/src/org/thoughtcrime/securesms/sms/MultipartSmsMessageHandler.java
index 977f9240d7..4ecaa778cd 100644
--- a/src/org/thoughtcrime/securesms/sms/MultipartSmsMessageHandler.java
+++ b/src/org/thoughtcrime/securesms/sms/MultipartSmsMessageHandler.java
@@ -56,6 +56,8 @@ public class MultipartSmsMessageHandler {
if (message.getWireType() == MultipartSmsTransportMessage.WIRETYPE_KEY) {
return new IncomingKeyExchangeMessage(message.getBaseMessage(), strippedMessage);
+ } else if (message.getWireType() == MultipartSmsTransportMessage.WIRETYPE_PREKEY) {
+ return new IncomingPreKeyBundleMessage(message.getBaseMessage(), strippedMessage);
} else {
return new IncomingEncryptedMessage(message.getBaseMessage(), strippedMessage);
}
@@ -67,6 +69,8 @@ public class MultipartSmsMessageHandler {
if (message.getWireType() == MultipartSmsTransportMessage.WIRETYPE_KEY) {
return new IncomingKeyExchangeMessage(message.getBaseMessage(), strippedMessage);
+ } else if (message.getWireType() == MultipartSmsTransportMessage.WIRETYPE_PREKEY) {
+ return new IncomingPreKeyBundleMessage(message.getBaseMessage(), strippedMessage);
} else {
return new IncomingEncryptedMessage(message.getBaseMessage(), strippedMessage);
}
diff --git a/src/org/thoughtcrime/securesms/sms/MultipartSmsTransportMessage.java b/src/org/thoughtcrime/securesms/sms/MultipartSmsTransportMessage.java
index 145c86eed2..4632fcfc37 100644
--- a/src/org/thoughtcrime/securesms/sms/MultipartSmsTransportMessage.java
+++ b/src/org/thoughtcrime/securesms/sms/MultipartSmsTransportMessage.java
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.sms;
import android.util.Log;
import org.thoughtcrime.securesms.protocol.KeyExchangeWirePrefix;
+import org.thoughtcrime.securesms.protocol.PrekeyBundleWirePrefix;
import org.thoughtcrime.securesms.protocol.SecureMessageWirePrefix;
import org.thoughtcrime.securesms.protocol.WirePrefix;
import org.whispersystems.textsecure.util.Base64;
@@ -22,6 +23,7 @@ public class MultipartSmsTransportMessage {
public static final int WIRETYPE_SECURE = 1;
public static final int WIRETYPE_KEY = 2;
+ public static final int WIRETYPE_PREKEY = 3;
private static final int VERSION_OFFSET = 0;
private static final int MULTIPART_OFFSET = 1;
@@ -33,9 +35,12 @@ public class MultipartSmsTransportMessage {
public MultipartSmsTransportMessage(IncomingTextMessage message) throws IOException {
this.message = message;
- this.wireType = WirePrefix.isEncryptedMessage(message.getMessageBody()) ? WIRETYPE_SECURE : WIRETYPE_KEY;
this.decodedMessage = Base64.decodeWithoutPadding(message.getMessageBody().substring(WirePrefix.PREFIX_SIZE));
+ if (WirePrefix.isEncryptedMessage(message.getMessageBody())) wireType = WIRETYPE_SECURE;
+ else if (WirePrefix.isPreKeyBundle(message.getMessageBody())) wireType = WIRETYPE_PREKEY;
+ else wireType = WIRETYPE_KEY;
+
Log.w(TAG, "Decoded message with version: " + getCurrentVersion());
}
@@ -151,8 +156,9 @@ public class MultipartSmsTransportMessage {
WirePrefix prefix;
- if (message.isKeyExchange()) prefix = new KeyExchangeWirePrefix();
- else prefix = new SecureMessageWirePrefix();
+ if (message.isKeyExchange()) prefix = new KeyExchangeWirePrefix();
+ else if (message.isPreKeyBundle()) prefix = new PrekeyBundleWirePrefix();
+ else prefix = new SecureMessageWirePrefix();
if (count == 1) return getSingleEncoded(decoded, prefix);
else return getMultiEncoded(decoded, prefix, count, identifier);
diff --git a/src/org/thoughtcrime/securesms/sms/OutgoingPrekeyBundleMessage.java b/src/org/thoughtcrime/securesms/sms/OutgoingPrekeyBundleMessage.java
new file mode 100644
index 0000000000..ae7f9329a4
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/sms/OutgoingPrekeyBundleMessage.java
@@ -0,0 +1,19 @@
+package org.thoughtcrime.securesms.sms;
+
+
+public class OutgoingPrekeyBundleMessage extends OutgoingTextMessage {
+
+ public OutgoingPrekeyBundleMessage(OutgoingTextMessage message, String body) {
+ super(message, body);
+ }
+
+ @Override
+ public boolean isPreKeyBundle() {
+ return true;
+ }
+
+ @Override
+ public OutgoingTextMessage withBody(String body) {
+ return new OutgoingPrekeyBundleMessage(this, body);
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/sms/OutgoingTextMessage.java b/src/org/thoughtcrime/securesms/sms/OutgoingTextMessage.java
index 0aafd52f1e..5eac49e7d7 100644
--- a/src/org/thoughtcrime/securesms/sms/OutgoingTextMessage.java
+++ b/src/org/thoughtcrime/securesms/sms/OutgoingTextMessage.java
@@ -19,8 +19,8 @@ public class OutgoingTextMessage {
}
protected OutgoingTextMessage(OutgoingTextMessage base, String body) {
- this.recipients = base.getRecipients();
- this.message = body;
+ this.recipients = base.getRecipients();
+ this.message = body;
}
public String getMessageBody() {
@@ -39,6 +39,10 @@ public class OutgoingTextMessage {
return false;
}
+ public boolean isPreKeyBundle() {
+ return false;
+ }
+
public static OutgoingTextMessage from(SmsMessageRecord record) {
if (record.isSecure()) {
return new OutgoingEncryptedMessage(record.getIndividualRecipient(), record.getBody().getBody());
diff --git a/src/org/thoughtcrime/securesms/transport/PushTransport.java b/src/org/thoughtcrime/securesms/transport/PushTransport.java
index b7f5aadb9c..0458fa2635 100644
--- a/src/org/thoughtcrime/securesms/transport/PushTransport.java
+++ b/src/org/thoughtcrime/securesms/transport/PushTransport.java
@@ -2,26 +2,27 @@ package org.thoughtcrime.securesms.transport;
import android.content.Context;
import android.util.Log;
+import android.util.Pair;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
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.IdentityKey;
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.PreKeyBundleMessage;
import org.whispersystems.textsecure.push.PreKeyEntity;
import org.whispersystems.textsecure.push.PushAttachmentData;
import org.whispersystems.textsecure.push.PushServiceSocket;
+import org.whispersystems.textsecure.push.PushTransportDetails;
import org.whispersystems.textsecure.push.RateLimitException;
-import org.whispersystems.textsecure.storage.SessionRecord;
import org.whispersystems.textsecure.util.PhoneNumberFormatter;
import java.io.IOException;
@@ -53,20 +54,14 @@ public class PushTransport extends BaseTransport {
String password = TextSecurePreferences.getPushServerPassword(context);
PushServiceSocket socket = new PushServiceSocket(context, localNumber, password);
- Recipient recipient = message.getIndividualRecipient();
- String plaintext = message.getBody().getBody();
- String recipientCanonicalNumber = PhoneNumberFormatter.formatNumber(recipient.getNumber(),
- localNumber);
+ Recipient recipient = message.getIndividualRecipient();
+ String plaintext = message.getBody().getBody();
+ String recipientCanonicalNumber = PhoneNumberFormatter.formatNumber(recipient.getNumber(),
+ localNumber);
- 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);
- }
+ Pair typeAndCiphertext = getEncryptedMessage(socket, recipient, recipientCanonicalNumber, plaintext);
+
+ socket.sendMessage(recipientCanonicalNumber, typeAndCiphertext.second, typeAndCiphertext.first);
context.sendBroadcast(constructSentIntent(context, message.getId(), message.getType()));
} catch (RateLimitException e) {
@@ -108,8 +103,42 @@ public class PushTransport extends BaseTransport {
return attachments;
}
- private byte[] getEncryptedMessageForNewSession(PushServiceSocket socket, Recipient recipient,
- String canonicalRecipientNumber, String plaintext)
+ private Pair getEncryptedMessage(PushServiceSocket socket, Recipient recipient,
+ String canonicalRecipientNumber, String plaintext)
+ throws IOException
+ {
+ if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) {
+ Log.w("PushTransport", "Sending standard ciphertext message...");
+ String ciphertext = getEncryptedMessageForExistingSession(recipient, plaintext);
+ return new Pair(TYPE_MESSAGE_CIPHERTEXT, ciphertext);
+ } else if (KeyUtil.isSessionFor(context, recipient)) {
+ Log.w("PushTransport", "Sending prekeybundle ciphertext message for existing session...");
+ String ciphertext = getEncryptedPrekeyBundleMessageForExistingSession(recipient, plaintext);
+ return new Pair(TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext);
+ } else {
+ Log.w("PushTransport", "Sending prekeybundle ciphertext message for new session...");
+ String ciphertext = getEncryptedPrekeyBundleMessageForNewSession(socket, recipient, canonicalRecipientNumber, plaintext);
+ return new Pair(TYPE_MESSAGE_PREKEY_BUNDLE, ciphertext);
+ }
+ }
+
+ private String getEncryptedPrekeyBundleMessageForExistingSession(Recipient recipient,
+ String plaintext)
+ {
+ IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
+ IdentityKey identityKey = identityKeyPair.getPublicKey();
+
+ MessageCipher message = new MessageCipher(context, masterSecret, identityKeyPair, new RawTransportDetails());
+ byte[] bundledMessage = message.encrypt(recipient, plaintext.getBytes());
+
+ PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(identityKey, bundledMessage);
+ return preKeyBundleMessage.serialize();
+ }
+
+ private String getEncryptedPrekeyBundleMessageForNewSession(PushServiceSocket socket,
+ Recipient recipient,
+ String canonicalRecipientNumber,
+ String plaintext)
throws IOException
{
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
@@ -123,15 +152,17 @@ public class PushTransport extends BaseTransport {
byte[] bundledMessage = message.encrypt(recipient, plaintext.getBytes());
PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(identityKey, bundledMessage);
- return preKeyBundleMessage.serialize().getBytes();
+ return preKeyBundleMessage.serialize();
}
- private byte[] getEncryptedMessageForExistingSession(Recipient recipient, String plaintext)
+ private String getEncryptedMessageForExistingSession(Recipient recipient, String plaintext)
throws IOException
{
- IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
- MessageCipher message = new MessageCipher(context, masterSecret, identityKeyPair, new TextTransport());
- return message.encrypt(recipient, plaintext.getBytes());
+ IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
+ MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair,
+ new PushTransportDetails());
+
+ return new String(messageCipher.encrypt(recipient, plaintext.getBytes()));
}
}
diff --git a/src/org/thoughtcrime/securesms/transport/SmsTransport.java b/src/org/thoughtcrime/securesms/transport/SmsTransport.java
index 51d0687eee..2ee86ead58 100644
--- a/src/org/thoughtcrime/securesms/transport/SmsTransport.java
+++ b/src/org/thoughtcrime/securesms/transport/SmsTransport.java
@@ -4,9 +4,12 @@ import android.app.PendingIntent;
import android.content.Context;
import android.telephony.SmsManager;
import android.util.Log;
+import android.util.Pair;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
+import org.thoughtcrime.securesms.sms.OutgoingPrekeyBundleMessage;import org.thoughtcrime.securesms.sms.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;
@@ -17,6 +20,7 @@ import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
+import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
import java.util.ArrayList;
@@ -43,9 +47,7 @@ public class SmsTransport extends BaseTransport {
OutgoingTextMessage transportMessage = OutgoingTextMessage.from(message);
if (message.isSecure()) {
- String encryptedMessage = getAsymmetricEncrypt(masterSecret, message.getBody().getBody(),
- message.getIndividualRecipient());
- transportMessage = transportMessage.withBody(encryptedMessage);
+ transportMessage = getAsymmetricEncrypt(masterSecret, transportMessage);
}
ArrayList messages = multipartMessageHandler.divideMessage(transportMessage);
@@ -139,9 +141,26 @@ public class SmsTransport extends BaseTransport {
return deliveredIntents;
}
- private String getAsymmetricEncrypt(MasterSecret masterSecret, String body, Recipient recipient) {
- IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
- MessageCipher message = new MessageCipher(context, masterSecret, identityKey, new SmsTransportDetails());
- return new String(message.encrypt(recipient, body.getBytes()));
+ private OutgoingTextMessage getAsymmetricEncrypt(MasterSecret masterSecret,
+ OutgoingTextMessage message)
+ {
+ Recipient recipient = message.getRecipients().getPrimaryRecipient();
+ String body = message.getMessageBody();
+ IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
+
+ if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) {
+ Log.w("SmsTransport", "Delivering standard ciphertext...");
+ MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey, new SmsTransportDetails());
+ byte[] ciphertext = messageCipher.encrypt(recipient, body.getBytes());
+
+ return message.withBody(new String(ciphertext));
+ } else {
+ Log.w("SmsTransport", "Delivering prekeybundle ciphertext...");
+ MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey, new RawTransportDetails());
+ byte[] bundledMessage = messageCipher.encrypt(recipient, body.getBytes());
+ PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(identityKey.getPublicKey(), bundledMessage);
+
+ return new OutgoingPrekeyBundleMessage(message, preKeyBundleMessage.serialize());
+ }
}
}
diff --git a/src/org/thoughtcrime/securesms/transport/UniversalTransport.java b/src/org/thoughtcrime/securesms/transport/UniversalTransport.java
index e468cc1c62..144c4592f6 100644
--- a/src/org/thoughtcrime/securesms/transport/UniversalTransport.java
+++ b/src/org/thoughtcrime/securesms/transport/UniversalTransport.java
@@ -1,3 +1,19 @@
+/**
+ * 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.thoughtcrime.securesms.transport;
import android.content.Context;