Added SMS transport support for PreKeyBundle messages.

1) Added SMS transport support.

2) Keep track of whether a PreKeyBundle message has gotten
   a response, and send them as subsequent messages until
   one has been received.
This commit is contained in:
Moxie Marlinspike 2013-08-21 19:34:11 -07:00
parent c3b8b62d32
commit 1bbcedabd4
14 changed files with 221 additions and 42 deletions

View File

@ -107,6 +107,11 @@ public class KeyUtil {
(SessionRecord.hasSession(context, recipient)); (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, public static boolean isIdentityKeyFor(Context context,
MasterSecret masterSecret, MasterSecret masterSecret,
CanonicalRecipientAddress recipient) CanonicalRecipientAddress recipient)

View File

@ -36,17 +36,17 @@ public class MessageCipher {
public static final int SUPPORTED_VERSION = 2; public static final int SUPPORTED_VERSION = 2;
public static final int CRADLE_AGREEMENT_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 SENDER_KEY_ID_LENGTH = 3;
private static final int RECEIVER_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; 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 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; 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; public 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 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 COUNTER_OFFSET = NEXT_KEY_OFFSET + NEXT_KEY_LENGTH;
private static final int TEXT_OFFSET = COUNTER_OFFSET + COUNTER_LENGTH; private static final int TEXT_OFFSET = COUNTER_OFFSET + COUNTER_LENGTH;

View File

@ -131,6 +131,7 @@ public class SessionCipher {
context.getSessionRecord().setSessionKey(context.getSessionKey()); context.getSessionRecord().setSessionKey(context.getSessionKey());
context.getSessionRecord().setSessionVersion(context.getNegotiatedVersion()); context.getSessionRecord().setSessionVersion(context.getNegotiatedVersion());
context.getSessionRecord().setPrekeyBundleRequired(false);
context.getSessionRecord().save(); context.getSessionRecord().save();
return plaintextWithPadding; return plaintextWithPadding;

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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));
}
}

View File

@ -36,8 +36,9 @@ import java.nio.channels.FileChannel;
*/ */
public class SessionRecord extends Record { 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 static final Object FILE_LOCK = new Object();
private int counter; private int counter;
@ -48,6 +49,7 @@ public class SessionRecord extends Record {
private IdentityKey identityKey; private IdentityKey identityKey;
private SessionKey sessionKeyRecord; private SessionKey sessionKeyRecord;
private boolean verifiedSessionKey; private boolean verifiedSessionKey;
private boolean prekeyBundleRequired;
private final MasterSecret masterSecret; private final MasterSecret masterSecret;
@ -63,7 +65,7 @@ public class SessionRecord extends Record {
} }
public static void delete(Context context, CanonicalRecipientAddress recipient) { 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) { public static boolean hasSession(Context context, CanonicalRecipientAddress recipient) {
@ -116,6 +118,14 @@ public class SessionRecord extends Record {
return this.identityKey; return this.identityKey;
} }
public boolean isPrekeyBundleRequired() {
return prekeyBundleRequired;
}
public void setPrekeyBundleRequired(boolean prekeyBundleRequired) {
this.prekeyBundleRequired = prekeyBundleRequired;
}
// public void setVerifiedSessionKey(boolean verifiedSessionKey) { // public void setVerifiedSessionKey(boolean verifiedSessionKey) {
// this.verifiedSessionKey = verifiedSessionKey; // this.verifiedSessionKey = verifiedSessionKey;
// } // }
@ -162,6 +172,7 @@ public class SessionRecord extends Record {
writeInteger(sessionVersion, out); writeInteger(sessionVersion, out);
writeIdentityKey(out); writeIdentityKey(out);
writeInteger(verifiedSessionKey ? 1 : 0, out); writeInteger(verifiedSessionKey ? 1 : 0, out);
writeInteger(prekeyBundleRequired ? 1 : 0, out);
if (sessionKeyRecord != null) if (sessionKeyRecord != null)
writeBlob(sessionKeyRecord.serialize(), out); writeBlob(sessionKeyRecord.serialize(), out);
@ -202,6 +213,10 @@ public class SessionRecord extends Record {
this.verifiedSessionKey = (readInteger(in) == 1); this.verifiedSessionKey = (readInteger(in) == 1);
} }
if (versionMarker >= 0X55555557) {
this.prekeyBundleRequired = (readInteger(in) == 1);
}
if (in.available() != 0) if (in.available() != 0)
this.sessionKeyRecord = new SessionKey(readBlob(in), masterSecret); this.sessionKeyRecord = new SessionKey(readBlob(in), masterSecret);
@ -226,4 +241,5 @@ public class SessionRecord extends Record {
return null; return null;
} }
} }

View File

@ -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 remote key ID: " + remoteKey.getId());
Log.w("KeyExchangeProcessor", "Received pre-key with local key ID: " + preKeyId); 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)) if (!PreKeyRecord.hasRecord(context, preKeyId))
throw new InvalidKeyIdException("No such prekey: " + preKeyId); throw new InvalidKeyIdException("No such prekey: " + preKeyId);
@ -152,6 +157,7 @@ public class KeyExchangeProcessor {
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes()); remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
sessionRecord.setIdentityKey(message.getIdentityKey()); sessionRecord.setIdentityKey(message.getIdentityKey());
sessionRecord.setSessionVersion(MessageCipher.SUPPORTED_VERSION); sessionRecord.setSessionVersion(MessageCipher.SUPPORTED_VERSION);
sessionRecord.setPrekeyBundleRequired(true);
sessionRecord.save(); sessionRecord.save();
DatabaseFactory.getIdentityDatabase(context) DatabaseFactory.getIdentityDatabase(context)

View File

@ -0,0 +1,8 @@
package org.thoughtcrime.securesms.protocol;
public class PrekeyBundleWirePrefix extends WirePrefix {
@Override
public String calculatePrefix(String message) {
return super.calculatePreKeyBundlePrefix(message);
}
}

View File

@ -56,6 +56,8 @@ public class MultipartSmsMessageHandler {
if (message.getWireType() == MultipartSmsTransportMessage.WIRETYPE_KEY) { if (message.getWireType() == MultipartSmsTransportMessage.WIRETYPE_KEY) {
return new IncomingKeyExchangeMessage(message.getBaseMessage(), strippedMessage); return new IncomingKeyExchangeMessage(message.getBaseMessage(), strippedMessage);
} else if (message.getWireType() == MultipartSmsTransportMessage.WIRETYPE_PREKEY) {
return new IncomingPreKeyBundleMessage(message.getBaseMessage(), strippedMessage);
} else { } else {
return new IncomingEncryptedMessage(message.getBaseMessage(), strippedMessage); return new IncomingEncryptedMessage(message.getBaseMessage(), strippedMessage);
} }
@ -67,6 +69,8 @@ public class MultipartSmsMessageHandler {
if (message.getWireType() == MultipartSmsTransportMessage.WIRETYPE_KEY) { if (message.getWireType() == MultipartSmsTransportMessage.WIRETYPE_KEY) {
return new IncomingKeyExchangeMessage(message.getBaseMessage(), strippedMessage); return new IncomingKeyExchangeMessage(message.getBaseMessage(), strippedMessage);
} else if (message.getWireType() == MultipartSmsTransportMessage.WIRETYPE_PREKEY) {
return new IncomingPreKeyBundleMessage(message.getBaseMessage(), strippedMessage);
} else { } else {
return new IncomingEncryptedMessage(message.getBaseMessage(), strippedMessage); return new IncomingEncryptedMessage(message.getBaseMessage(), strippedMessage);
} }

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.sms;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.protocol.KeyExchangeWirePrefix; import org.thoughtcrime.securesms.protocol.KeyExchangeWirePrefix;
import org.thoughtcrime.securesms.protocol.PrekeyBundleWirePrefix;
import org.thoughtcrime.securesms.protocol.SecureMessageWirePrefix; import org.thoughtcrime.securesms.protocol.SecureMessageWirePrefix;
import org.thoughtcrime.securesms.protocol.WirePrefix; import org.thoughtcrime.securesms.protocol.WirePrefix;
import org.whispersystems.textsecure.util.Base64; 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_SECURE = 1;
public static final int WIRETYPE_KEY = 2; 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 VERSION_OFFSET = 0;
private static final int MULTIPART_OFFSET = 1; private static final int MULTIPART_OFFSET = 1;
@ -33,9 +35,12 @@ public class MultipartSmsTransportMessage {
public MultipartSmsTransportMessage(IncomingTextMessage message) throws IOException { public MultipartSmsTransportMessage(IncomingTextMessage message) throws IOException {
this.message = message; this.message = message;
this.wireType = WirePrefix.isEncryptedMessage(message.getMessageBody()) ? WIRETYPE_SECURE : WIRETYPE_KEY;
this.decodedMessage = Base64.decodeWithoutPadding(message.getMessageBody().substring(WirePrefix.PREFIX_SIZE)); 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()); Log.w(TAG, "Decoded message with version: " + getCurrentVersion());
} }
@ -151,8 +156,9 @@ public class MultipartSmsTransportMessage {
WirePrefix prefix; WirePrefix prefix;
if (message.isKeyExchange()) prefix = new KeyExchangeWirePrefix(); if (message.isKeyExchange()) prefix = new KeyExchangeWirePrefix();
else prefix = new SecureMessageWirePrefix(); else if (message.isPreKeyBundle()) prefix = new PrekeyBundleWirePrefix();
else prefix = new SecureMessageWirePrefix();
if (count == 1) return getSingleEncoded(decoded, prefix); if (count == 1) return getSingleEncoded(decoded, prefix);
else return getMultiEncoded(decoded, prefix, count, identifier); else return getMultiEncoded(decoded, prefix, count, identifier);

View File

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

View File

@ -19,8 +19,8 @@ public class OutgoingTextMessage {
} }
protected OutgoingTextMessage(OutgoingTextMessage base, String body) { protected OutgoingTextMessage(OutgoingTextMessage base, String body) {
this.recipients = base.getRecipients(); this.recipients = base.getRecipients();
this.message = body; this.message = body;
} }
public String getMessageBody() { public String getMessageBody() {
@ -39,6 +39,10 @@ public class OutgoingTextMessage {
return false; return false;
} }
public boolean isPreKeyBundle() {
return false;
}
public static OutgoingTextMessage from(SmsMessageRecord record) { public static OutgoingTextMessage from(SmsMessageRecord record) {
if (record.isSecure()) { if (record.isSecure()) {
return new OutgoingEncryptedMessage(record.getIndividualRecipient(), record.getBody().getBody()); return new OutgoingEncryptedMessage(record.getIndividualRecipient(), record.getBody().getBody());

View File

@ -2,26 +2,27 @@ 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 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;
import org.thoughtcrime.securesms.mms.PartParser; import org.thoughtcrime.securesms.mms.PartParser;
import org.thoughtcrime.securesms.mms.TextTransport;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.RawTransportDetails; import org.thoughtcrime.securesms.sms.RawTransportDetails;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.IdentityKeyPair; import org.whispersystems.textsecure.crypto.IdentityKeyPair;
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.PreKeyBundleMessage; import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
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.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.storage.SessionRecord;
import org.whispersystems.textsecure.util.PhoneNumberFormatter; import org.whispersystems.textsecure.util.PhoneNumberFormatter;
import java.io.IOException; import java.io.IOException;
@ -53,20 +54,14 @@ public class PushTransport extends BaseTransport {
String password = TextSecurePreferences.getPushServerPassword(context); String password = TextSecurePreferences.getPushServerPassword(context);
PushServiceSocket socket = new PushServiceSocket(context, localNumber, password); PushServiceSocket socket = new PushServiceSocket(context, localNumber, password);
Recipient recipient = message.getIndividualRecipient(); Recipient recipient = message.getIndividualRecipient();
String plaintext = message.getBody().getBody(); String plaintext = message.getBody().getBody();
String recipientCanonicalNumber = PhoneNumberFormatter.formatNumber(recipient.getNumber(), String recipientCanonicalNumber = PhoneNumberFormatter.formatNumber(recipient.getNumber(),
localNumber); localNumber);
if (SessionRecord.hasSession(context, recipient)) { Pair<Integer, String> typeAndCiphertext = getEncryptedMessage(socket, recipient, recipientCanonicalNumber, plaintext);
byte[] cipherText = getEncryptedMessageForExistingSession(recipient, plaintext);
socket.sendMessage(recipientCanonicalNumber, new String(cipherText), TYPE_MESSAGE_CIPHERTEXT); socket.sendMessage(recipientCanonicalNumber, typeAndCiphertext.second, typeAndCiphertext.first);
} 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())); context.sendBroadcast(constructSentIntent(context, message.getId(), message.getType()));
} catch (RateLimitException e) { } catch (RateLimitException e) {
@ -108,8 +103,42 @@ public class PushTransport extends BaseTransport {
return attachments; return attachments;
} }
private byte[] getEncryptedMessageForNewSession(PushServiceSocket socket, Recipient recipient, private Pair<Integer, String> getEncryptedMessage(PushServiceSocket socket, Recipient recipient,
String canonicalRecipientNumber, String plaintext) 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<Integer, String>(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<Integer, String>(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<Integer, String>(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 throws IOException
{ {
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
@ -123,15 +152,17 @@ public class PushTransport extends BaseTransport {
byte[] bundledMessage = message.encrypt(recipient, plaintext.getBytes()); byte[] bundledMessage = message.encrypt(recipient, plaintext.getBytes());
PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(identityKey, bundledMessage); 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 throws IOException
{ {
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
MessageCipher message = new MessageCipher(context, masterSecret, identityKeyPair, new TextTransport()); MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKeyPair,
return message.encrypt(recipient, plaintext.getBytes()); new PushTransportDetails());
return new String(messageCipher.encrypt(recipient, plaintext.getBytes()));
} }
} }

View File

@ -4,9 +4,12 @@ import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.telephony.SmsManager; import android.telephony.SmsManager;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; 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.IdentityKeyPair;
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.thoughtcrime.securesms.database.model.SmsMessageRecord; 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.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.protocol.PreKeyBundleMessage;
import java.util.ArrayList; import java.util.ArrayList;
@ -43,9 +47,7 @@ public class SmsTransport extends BaseTransport {
OutgoingTextMessage transportMessage = OutgoingTextMessage.from(message); OutgoingTextMessage transportMessage = OutgoingTextMessage.from(message);
if (message.isSecure()) { if (message.isSecure()) {
String encryptedMessage = getAsymmetricEncrypt(masterSecret, message.getBody().getBody(), transportMessage = getAsymmetricEncrypt(masterSecret, transportMessage);
message.getIndividualRecipient());
transportMessage = transportMessage.withBody(encryptedMessage);
} }
ArrayList<String> messages = multipartMessageHandler.divideMessage(transportMessage); ArrayList<String> messages = multipartMessageHandler.divideMessage(transportMessage);
@ -139,9 +141,26 @@ public class SmsTransport extends BaseTransport {
return deliveredIntents; return deliveredIntents;
} }
private String getAsymmetricEncrypt(MasterSecret masterSecret, String body, Recipient recipient) { private OutgoingTextMessage getAsymmetricEncrypt(MasterSecret masterSecret,
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret); OutgoingTextMessage message)
MessageCipher message = new MessageCipher(context, masterSecret, identityKey, new SmsTransportDetails()); {
return new String(message.encrypt(recipient, body.getBytes())); 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());
}
} }
} }

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms.transport; package org.thoughtcrime.securesms.transport;
import android.content.Context; import android.content.Context;