mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-25 17:27:45 +00:00
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:
parent
c3b8b62d32
commit
1bbcedabd4
@ -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)
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
package org.thoughtcrime.securesms.protocol;
|
||||||
|
|
||||||
|
public class PrekeyBundleWirePrefix extends WirePrefix {
|
||||||
|
@Override
|
||||||
|
public String calculatePrefix(String message) {
|
||||||
|
return super.calculatePreKeyBundlePrefix(message);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,6 +157,7 @@ public class MultipartSmsTransportMessage {
|
|||||||
WirePrefix prefix;
|
WirePrefix prefix;
|
||||||
|
|
||||||
if (message.isKeyExchange()) prefix = new KeyExchangeWirePrefix();
|
if (message.isKeyExchange()) prefix = new KeyExchangeWirePrefix();
|
||||||
|
else if (message.isPreKeyBundle()) prefix = new PrekeyBundleWirePrefix();
|
||||||
else prefix = new SecureMessageWirePrefix();
|
else prefix = new SecureMessageWirePrefix();
|
||||||
|
|
||||||
if (count == 1) return getSingleEncoded(decoded, prefix);
|
if (count == 1) return getSingleEncoded(decoded, prefix);
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
|
@ -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;
|
||||||
@ -58,15 +59,9 @@ public class PushTransport extends BaseTransport {
|
|||||||
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,9 +103,43 @@ 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
|
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);
|
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
||||||
IdentityKey identityKey = identityKeyPair.getPublicKey();
|
IdentityKey identityKey = identityKeyPair.getPublicKey();
|
||||||
@ -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()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
OutgoingTextMessage message)
|
||||||
|
{
|
||||||
|
Recipient recipient = message.getRecipients().getPrimaryRecipient();
|
||||||
|
String body = message.getMessageBody();
|
||||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
||||||
MessageCipher message = new MessageCipher(context, masterSecret, identityKey, new SmsTransportDetails());
|
|
||||||
return new String(message.encrypt(recipient, body.getBytes()));
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user