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

@@ -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<Integer, String> 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<Integer, String> 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<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
{
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()));
}
}

View File

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

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;
import android.content.Context;