mirror of
https://github.com/oxen-io/session-android.git
synced 2025-10-26 11:21:55 +00:00
Basic support for prekeybundle message delivery and receipt.
This commit is contained in:
@@ -31,7 +31,7 @@ import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.thoughtcrime.securesms.crypto.InvalidVersionException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidVersionException;
|
||||
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
|
||||
@@ -194,7 +194,9 @@ public class RegistrationProgressActivity extends SherlockActivity {
|
||||
intent.putExtra("master_secret", masterSecret);
|
||||
startService(intent);
|
||||
} else {
|
||||
startActivity(new Intent(this, RegistrationActivity.class));
|
||||
Intent intent = new Intent(this, RegistrationActivity.class);
|
||||
intent.putExtra("master_secret", masterSecret);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -408,6 +410,7 @@ public class RegistrationProgressActivity extends SherlockActivity {
|
||||
shutdownService();
|
||||
|
||||
Intent activityIntent = new Intent(RegistrationProgressActivity.this, RegistrationActivity.class);
|
||||
activityIntent.putExtra("master_secret", masterSecret);
|
||||
startActivity(activityIntent);
|
||||
finish();
|
||||
}
|
||||
|
||||
@@ -34,8 +34,11 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidVersionException;
|
||||
import org.whispersystems.textsecure.crypto.SessionCipher;
|
||||
import org.whispersystems.textsecure.crypto.protocol.EncryptedMessage;
|
||||
import org.whispersystems.textsecure.util.Hex;
|
||||
@@ -195,7 +198,8 @@ public class DecryptingQueue {
|
||||
|
||||
synchronized (SessionCipher.CIPHER_LOCK) {
|
||||
Log.w("DecryptingQueue", "Decrypting: " + Hex.toString(ciphertextPduBytes));
|
||||
EncryptedMessage message = new EncryptedMessage(context, masterSecret, new TextTransport());
|
||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
||||
EncryptedMessage message = new EncryptedMessage(context, masterSecret, identityKey, new TextTransport());
|
||||
|
||||
try {
|
||||
plaintextPduBytes = message.decrypt(recipient, ciphertextPduBytes);
|
||||
@@ -275,7 +279,9 @@ public class DecryptingQueue {
|
||||
return;
|
||||
}
|
||||
|
||||
EncryptedMessage message = new EncryptedMessage(context, masterSecret, new SmsTransportDetails());
|
||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
||||
EncryptedMessage message = new EncryptedMessage(context, masterSecret, identityKey, new SmsTransportDetails());
|
||||
|
||||
plaintextBody = new String(message.decrypt(recipient, body.getBytes()));
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w("DecryptionQueue", e);
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.spongycastle.crypto.signers.ECDSASigner;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.KeyUtil;
|
||||
import org.whispersystems.textsecure.crypto.MasterCipher;
|
||||
@@ -75,6 +76,22 @@ public class IdentityKeyUtil {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static IdentityKeyPair getIdentityKeyPair(Context context, MasterSecret masterSecret) {
|
||||
if (!hasIdentityKey(context))
|
||||
return null;
|
||||
|
||||
try {
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
IdentityKey publicKey = getIdentityKey(context);
|
||||
byte[] privateKeyBytes = Base64.decode(retrieve(context, IDENTITY_PRIVATE_KEY_PREF));
|
||||
ECPrivateKeyParameters privateKey = masterCipher.decryptKey(privateKeyBytes);
|
||||
|
||||
return new IdentityKeyPair(publicKey, privateKey);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getFingerprint(Context context) {
|
||||
if (!hasIdentityKey(context)) return null;
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 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.crypto;
|
||||
|
||||
public class InvalidVersionException extends Exception {
|
||||
|
||||
public InvalidVersionException() {
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidVersionException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidVersionException(Throwable throwable) {
|
||||
super(throwable);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
public InvalidVersionException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,12 +23,16 @@ import android.util.Log;
|
||||
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.crypto.KeyPair;
|
||||
import org.whispersystems.textsecure.crypto.KeyUtil;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.PublicKey;
|
||||
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
|
||||
import org.whispersystems.textsecure.crypto.protocol.EncryptedMessage;
|
||||
import org.whispersystems.textsecure.push.PreKeyEntity;
|
||||
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
||||
import org.whispersystems.textsecure.storage.LocalKeyRecord;
|
||||
import org.whispersystems.textsecure.storage.PreKeyRecord;
|
||||
import org.whispersystems.textsecure.storage.RemoteKeyRecord;
|
||||
import org.whispersystems.textsecure.storage.SessionRecord;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@@ -72,6 +76,10 @@ public class KeyExchangeProcessor {
|
||||
return isTrusted(message.getIdentityKey());
|
||||
}
|
||||
|
||||
public boolean isTrusted(PreKeyBundleMessage message) {
|
||||
return isTrusted(message.getIdentityKey());
|
||||
}
|
||||
|
||||
public boolean isTrusted(IdentityKey identityKey) {
|
||||
return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret, recipient,
|
||||
identityKey);
|
||||
@@ -94,6 +102,40 @@ public class KeyExchangeProcessor {
|
||||
(localKeyRecord.getCurrentKeyPair() != null && localKeyRecord.getCurrentKeyPair().getId() != responseKeyId);
|
||||
}
|
||||
|
||||
public void processKeyExchangeMessage(PreKeyBundleMessage message) throws InvalidKeyIdException {
|
||||
int preKeyId = message.getPreKeyId();
|
||||
PublicKey remoteKey = message.getPublicKey();
|
||||
IdentityKey remoteIdentity = message.getIdentityKey();
|
||||
|
||||
Log.w("KeyExchangeProcessor", "Received pre-key with remote key ID: " + remoteKey.getId());
|
||||
Log.w("KeyExchangeProcessor", "Received pre-key with local key ID: " + preKeyId);
|
||||
|
||||
if (!PreKeyRecord.hasRecord(context, preKeyId))
|
||||
throw new InvalidKeyIdException("No such prekey: " + preKeyId);
|
||||
|
||||
PreKeyRecord preKeyRecord = new PreKeyRecord(context, masterSecret, preKeyId);
|
||||
KeyPair preKeyPair = new KeyPair(preKeyId, preKeyRecord.getKeyPair().getKeyPair(), masterSecret);
|
||||
|
||||
localKeyRecord.setCurrentKeyPair(preKeyPair);
|
||||
localKeyRecord.setNextKeyPair(preKeyPair);
|
||||
|
||||
remoteKeyRecord.setCurrentRemoteKey(remoteKey);
|
||||
remoteKeyRecord.setLastRemoteKey(remoteKey);
|
||||
|
||||
sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(),
|
||||
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
|
||||
sessionRecord.setIdentityKey(remoteIdentity);
|
||||
sessionRecord.setSessionVersion(Math.min(message.getSupportedVersion(), EncryptedMessage.SUPPORTED_VERSION));
|
||||
|
||||
|
||||
localKeyRecord.save();
|
||||
remoteKeyRecord.save();
|
||||
sessionRecord.save();
|
||||
|
||||
DatabaseFactory.getIdentityDatabase(context)
|
||||
.saveIdentity(masterSecret, recipient, remoteIdentity);
|
||||
}
|
||||
|
||||
public void processKeyExchangeMessage(PreKeyEntity message) {
|
||||
PublicKey remoteKey = new PublicKey(message.getKeyId(), message.getPublicKey());
|
||||
remoteKeyRecord.setCurrentRemoteKey(remoteKey);
|
||||
@@ -101,6 +143,8 @@ public class KeyExchangeProcessor {
|
||||
remoteKeyRecord.save();
|
||||
|
||||
localKeyRecord = KeyUtil.initializeRecordFor(recipient, context, masterSecret);
|
||||
localKeyRecord.setNextKeyPair(localKeyRecord.getCurrentKeyPair());
|
||||
localKeyRecord.save();
|
||||
|
||||
sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(),
|
||||
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
|
||||
|
||||
@@ -20,7 +20,7 @@ import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.InvalidVersionException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidVersionException;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
@@ -96,7 +96,7 @@ public class KeyExchangeMessage {
|
||||
if (messageVersion > EncryptedMessage.SUPPORTED_VERSION)
|
||||
throw new InvalidVersionException("Key exchange with version: " + messageVersion +
|
||||
" but we only support: " + EncryptedMessage.SUPPORTED_VERSION);
|
||||
|
||||
|
||||
if (messageVersion >= 1)
|
||||
keyBytes = Base64.decodeWithoutPadding(messageBody);
|
||||
|
||||
|
||||
@@ -27,9 +27,11 @@ public interface MmsSmsColumns {
|
||||
BASE_SENDING_TYPE, BASE_SENT_FAILED_TYPE};
|
||||
|
||||
// Key Exchange Information
|
||||
protected static final long KEY_EXCHANGE_BIT = 0x8000;
|
||||
protected static final long KEY_EXCHANGE_STALE_BIT = 0x4000;
|
||||
protected static final long KEY_EXCHANGE_PROCESSED_BIT = 0x2000;
|
||||
protected static final long KEY_EXCHANGE_BIT = 0x8000;
|
||||
protected static final long KEY_EXCHANGE_STALE_BIT = 0x4000;
|
||||
protected static final long KEY_EXCHANGE_PROCESSED_BIT = 0x2000;
|
||||
protected static final long KEY_EXCHANGE_CORRUPTED_BIT = 0x1000;
|
||||
protected static final long KEY_EXCHANGE_INVALID_VERSION_BIT = 0x800;
|
||||
|
||||
// Secure Message Information
|
||||
protected static final long SECURE_MESSAGE_BIT = 0x800000;
|
||||
@@ -81,6 +83,14 @@ public interface MmsSmsColumns {
|
||||
return (type & KEY_EXCHANGE_PROCESSED_BIT) != 0;
|
||||
}
|
||||
|
||||
public static boolean isCorruptedKeyExchange(long type) {
|
||||
return (type & KEY_EXCHANGE_CORRUPTED_BIT) != 0;
|
||||
}
|
||||
|
||||
public static boolean isInvalidVersionKeyExchange(long type) {
|
||||
return (type & KEY_EXCHANGE_INVALID_VERSION_BIT) != 0;
|
||||
}
|
||||
|
||||
public static boolean isSymmetricEncryption(long type) {
|
||||
return (type & ENCRYPTION_SYMMETRIC_BIT) != 0;
|
||||
}
|
||||
|
||||
@@ -235,8 +235,10 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
|
||||
protected Pair<Long, Long> insertMessageInbox(IncomingTextMessage message, long type) {
|
||||
if (message.isKeyExchange()) {
|
||||
type |= Types.KEY_EXCHANGE_BIT;
|
||||
if (((IncomingKeyExchangeMessage)message).isStale()) type |= Types.KEY_EXCHANGE_STALE_BIT;
|
||||
else if (((IncomingKeyExchangeMessage)message).isProcessed()) type |= Types.KEY_EXCHANGE_PROCESSED_BIT;
|
||||
if (((IncomingKeyExchangeMessage)message).isStale()) type |= Types.KEY_EXCHANGE_STALE_BIT;
|
||||
else if (((IncomingKeyExchangeMessage)message).isProcessed()) type |= Types.KEY_EXCHANGE_PROCESSED_BIT;
|
||||
else if (((IncomingKeyExchangeMessage)message).isCorrupted()) type |= Types.KEY_EXCHANGE_CORRUPTED_BIT;
|
||||
else if (((IncomingKeyExchangeMessage)message).isInvalidVersion()) type |= Types.KEY_EXCHANGE_INVALID_VERSION_BIT;
|
||||
} else if (message.isSecureMessage()) {
|
||||
type |= Types.SECURE_MESSAGE_BIT;
|
||||
type |= Types.ENCRYPTION_REMOTE_BIT;
|
||||
|
||||
@@ -103,6 +103,14 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
return SmsDatabase.Types.isProcessedKeyExchange(type);
|
||||
}
|
||||
|
||||
public boolean isCorruptedKeyExchange() {
|
||||
return SmsDatabase.Types.isCorruptedKeyExchange(type);
|
||||
}
|
||||
|
||||
public boolean isInvalidVersionKeyExchange() {
|
||||
return SmsDatabase.Types.isInvalidVersionKeyExchange(type);
|
||||
}
|
||||
|
||||
public Recipient getIndividualRecipient() {
|
||||
return individualRecipient;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.textsecure.push.IncomingPushMessage;
|
||||
import org.whispersystems.textsecure.push.PushServiceSocket;
|
||||
import org.whispersystems.textsecure.push.RateLimitException;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -73,6 +72,7 @@ public class GcmIntentService extends GCMBaseIntentService {
|
||||
Intent receivedIntent = new Intent(context, SendReceiveService.class);
|
||||
receivedIntent.setAction(SendReceiveService.RECEIVE_SMS_ACTION);
|
||||
receivedIntent.putParcelableArrayListExtra("text_messages", messages);
|
||||
receivedIntent.putExtra("push_type", message.getType());
|
||||
context.startService(receivedIntent);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,10 @@ public abstract class WirePrefix {
|
||||
return verifyPrefix("?TSM", message);
|
||||
}
|
||||
|
||||
public static boolean isPreKeyBundle(String message) {
|
||||
return verifyPrefix("?TSP", message);
|
||||
}
|
||||
|
||||
public static String calculateKeyExchangePrefix(String message) {
|
||||
return calculatePrefix(("?TSK" + message).getBytes(), PREFIX_BYTES);
|
||||
}
|
||||
@@ -55,6 +59,10 @@ public abstract class WirePrefix {
|
||||
return calculatePrefix(("?TSM" + message).getBytes(), PREFIX_BYTES);
|
||||
}
|
||||
|
||||
public static String calculatePreKeyBundlePrefix(String message) {
|
||||
return calculatePrefix(("?TSP" + message).getBytes(), PREFIX_BYTES);
|
||||
}
|
||||
|
||||
private static boolean verifyPrefix(String prefixType, String message) {
|
||||
if (message.length() <= PREFIX_SIZE)
|
||||
return false;
|
||||
|
||||
@@ -22,8 +22,11 @@ import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.DecryptingQueue;
|
||||
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingPreKeyBundleMessage;
|
||||
import org.thoughtcrime.securesms.transport.PushTransport;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.thoughtcrime.securesms.crypto.InvalidVersionException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidVersionException;
|
||||
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
@@ -38,6 +41,8 @@ import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
|
||||
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -48,15 +53,32 @@ public class SmsReceiver {
|
||||
private final Context context;
|
||||
|
||||
public SmsReceiver(Context context) {
|
||||
this.context = context;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
private IncomingTextMessage assembleMessageFragments(List<IncomingTextMessage> messages, int pushType) {
|
||||
if (messages.size() != 1) return assembleMessageFragments(messages);
|
||||
|
||||
IncomingTextMessage message = messages.get(0);
|
||||
|
||||
switch (pushType) {
|
||||
case PushTransport.TYPE_MESSAGE_CIPHERTEXT:
|
||||
return new IncomingEncryptedMessage(message, message.getMessageBody());
|
||||
case PushTransport.TYPE_MESSAGE_PREKEY_BUNDLE:
|
||||
return new IncomingPreKeyBundleMessage(message, message.getMessageBody());
|
||||
case PushTransport.TYPE_MESSAGE_KEY_EXCHANGE:
|
||||
return new IncomingKeyExchangeMessage(message, message.getMessageBody());
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private IncomingTextMessage assembleMessageFragments(List<IncomingTextMessage> messages) {
|
||||
IncomingTextMessage message = new IncomingTextMessage(messages);
|
||||
|
||||
if (WirePrefix.isEncryptedMessage(message.getMessageBody()) ||
|
||||
WirePrefix.isKeyExchange(message.getMessageBody()))
|
||||
WirePrefix.isKeyExchange(message.getMessageBody()) ||
|
||||
WirePrefix.isPreKeyBundle(message.getMessageBody()))
|
||||
{
|
||||
return multipartMessageHandler.processPotentialMultipartMessage(message);
|
||||
} else {
|
||||
@@ -91,6 +113,43 @@ public class SmsReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
private Pair<Long, Long> storePreKeyBundledMessage(MasterSecret masterSecret,
|
||||
IncomingKeyExchangeMessage message)
|
||||
{
|
||||
Log.w("SmsReceiver", "Processing prekey message...");
|
||||
|
||||
try {
|
||||
Recipient recipient = new Recipient(null, message.getSender(), null, null);
|
||||
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient);
|
||||
PreKeyBundleMessage preKeyExchange = new PreKeyBundleMessage(message.getMessageBody());
|
||||
|
||||
if (processor.isTrusted(preKeyExchange)) {
|
||||
processor.processKeyExchangeMessage(preKeyExchange);
|
||||
|
||||
IncomingEncryptedMessage bundledMessage = new IncomingEncryptedMessage(message, preKeyExchange.getBundledMessage());
|
||||
Pair<Long, Long> messageAndThreadId = storeSecureMessage(masterSecret, bundledMessage);
|
||||
|
||||
Intent intent = new Intent(KeyExchangeProcessor.SECURITY_UPDATE_EVENT);
|
||||
intent.putExtra("thread_id", messageAndThreadId.second);
|
||||
intent.setPackage(context.getPackageName());
|
||||
context.sendBroadcast(intent, KeyCachingService.KEY_PERMISSION);
|
||||
|
||||
return messageAndThreadId;
|
||||
}
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.w("SmsReceiver", e);
|
||||
message.setCorrupted(true);
|
||||
} catch (InvalidVersionException e) {
|
||||
Log.w("SmsReceiver", e);
|
||||
message.setInvalidVersion(true);
|
||||
} catch (InvalidKeyIdException e) {
|
||||
Log.w("SmsReceiver", e);
|
||||
message.setStale(true);
|
||||
}
|
||||
|
||||
return storeStandardMessage(masterSecret, message);
|
||||
}
|
||||
|
||||
private Pair<Long, Long> storeKeyExchangeMessage(MasterSecret masterSecret,
|
||||
IncomingKeyExchangeMessage message)
|
||||
{
|
||||
@@ -114,8 +173,10 @@ public class SmsReceiver {
|
||||
}
|
||||
} catch (InvalidVersionException e) {
|
||||
Log.w("SmsReceiver", e);
|
||||
message.setInvalidVersion(true);
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.w("SmsReceiver", e);
|
||||
message.setCorrupted(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,13 +185,19 @@ public class SmsReceiver {
|
||||
|
||||
private Pair<Long, Long> storeMessage(MasterSecret masterSecret, IncomingTextMessage message) {
|
||||
if (message.isSecureMessage()) return storeSecureMessage(masterSecret, message);
|
||||
else if (message.isKeyExchange()) return storeKeyExchangeMessage(masterSecret, (IncomingKeyExchangeMessage)message);
|
||||
else if (message.isPreKeyBundle()) return storePreKeyBundledMessage(masterSecret, (IncomingKeyExchangeMessage) message);
|
||||
else if (message.isKeyExchange()) return storeKeyExchangeMessage(masterSecret, (IncomingKeyExchangeMessage) message);
|
||||
else return storeStandardMessage(masterSecret, message);
|
||||
}
|
||||
|
||||
private void handleReceiveMessage(MasterSecret masterSecret, Intent intent) {
|
||||
List<IncomingTextMessage> messagesList = intent.getExtras().getParcelableArrayList("text_messages");
|
||||
IncomingTextMessage message = assembleMessageFragments(messagesList);
|
||||
int pushType = intent.getIntExtra("push_type", -1);
|
||||
|
||||
IncomingTextMessage message;
|
||||
|
||||
if (pushType != -1) message = assembleMessageFragments(messagesList, pushType);
|
||||
else message = assembleMessageFragments(messagesList);
|
||||
|
||||
if (message != null) {
|
||||
Pair<Long, Long> messageAndThreadId = storeMessage(masterSecret, message);
|
||||
|
||||
@@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.sms;
|
||||
|
||||
public class IncomingEncryptedMessage extends IncomingTextMessage {
|
||||
|
||||
IncomingEncryptedMessage(IncomingTextMessage base, String newBody) {
|
||||
public IncomingEncryptedMessage(IncomingTextMessage base, String newBody) {
|
||||
super(base, newBody);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,17 @@ public class IncomingKeyExchangeMessage extends IncomingTextMessage {
|
||||
|
||||
private boolean isStale;
|
||||
private boolean isProcessed;
|
||||
private boolean isCorrupted;
|
||||
private boolean isInvalidVersion;
|
||||
|
||||
IncomingKeyExchangeMessage(IncomingTextMessage base, String newBody) {
|
||||
public IncomingKeyExchangeMessage(IncomingTextMessage base, String newBody) {
|
||||
super(base, newBody);
|
||||
|
||||
if (base instanceof IncomingKeyExchangeMessage) {
|
||||
this.isStale = ((IncomingKeyExchangeMessage)base).isStale;
|
||||
this.isProcessed = ((IncomingKeyExchangeMessage)base).isProcessed;
|
||||
this.isStale = ((IncomingKeyExchangeMessage)base).isStale;
|
||||
this.isProcessed = ((IncomingKeyExchangeMessage)base).isProcessed;
|
||||
this.isCorrupted = ((IncomingKeyExchangeMessage)base).isCorrupted;
|
||||
this.isInvalidVersion = ((IncomingKeyExchangeMessage)base).isInvalidVersion;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +39,22 @@ public class IncomingKeyExchangeMessage extends IncomingTextMessage {
|
||||
this.isProcessed = isProcessed;
|
||||
}
|
||||
|
||||
public boolean isCorrupted() {
|
||||
return isCorrupted;
|
||||
}
|
||||
|
||||
public void setCorrupted(boolean isCorrupted) {
|
||||
this.isCorrupted = isCorrupted;
|
||||
}
|
||||
|
||||
public boolean isInvalidVersion() {
|
||||
return isInvalidVersion;
|
||||
}
|
||||
|
||||
public void setInvalidVersion(boolean isInvalidVersion) {
|
||||
this.isInvalidVersion = isInvalidVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isKeyExchange() {
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.thoughtcrime.securesms.sms;
|
||||
|
||||
import org.whispersystems.textsecure.push.IncomingPushMessage;
|
||||
|
||||
public class IncomingPreKeyBundleMessage extends IncomingKeyExchangeMessage {
|
||||
|
||||
public IncomingPreKeyBundleMessage(IncomingTextMessage base, String newBody) {
|
||||
super(base, newBody);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IncomingPreKeyBundleMessage withMessageBody(String messageBody) {
|
||||
return new IncomingPreKeyBundleMessage(this, messageBody);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPreKeyBundle() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -126,6 +126,10 @@ public class IncomingTextMessage implements Parcelable {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isPreKeyBundle() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
|
||||
27
src/org/thoughtcrime/securesms/sms/RawTransportDetails.java
Normal file
27
src/org/thoughtcrime/securesms/sms/RawTransportDetails.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package org.thoughtcrime.securesms.sms;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.TransportDetails;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class RawTransportDetails implements TransportDetails {
|
||||
@Override
|
||||
public byte[] getStrippedPaddingMessageBody(byte[] messageWithPadding) {
|
||||
return messageWithPadding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getPaddedMessageBody(byte[] messageBody) {
|
||||
return messageBody;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEncodedMessage(byte[] messageWithMac) {
|
||||
return messageWithMac;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getDecodedMessage(byte[] encodedMessageBytes) throws IOException {
|
||||
return encodedMessageBytes;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,9 @@ import android.telephony.TelephonyManager;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.SessionCipher;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
@@ -134,7 +137,8 @@ public class MmsTransport {
|
||||
}
|
||||
|
||||
private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipient, byte[] pduBytes) {
|
||||
EncryptedMessage message = new EncryptedMessage(context, masterSecret, new TextTransport());
|
||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
||||
EncryptedMessage message = new EncryptedMessage(context, masterSecret, identityKey, new TextTransport());
|
||||
return message.encrypt(new Recipient(null, recipient, null, null), pduBytes);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,16 +3,20 @@ package org.thoughtcrime.securesms.transport;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
|
||||
import org.thoughtcrime.securesms.mms.TextTransport;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.mms.PartParser;
|
||||
import org.thoughtcrime.securesms.mms.TextTransport;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.sms.RawTransportDetails;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.textsecure.crypto.SessionCipher;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
|
||||
import org.whispersystems.textsecure.crypto.protocol.EncryptedMessage;
|
||||
import org.whispersystems.textsecure.push.PreKeyEntity;
|
||||
import org.whispersystems.textsecure.push.PushAttachmentData;
|
||||
@@ -31,6 +35,11 @@ import ws.com.google.android.mms.pdu.SendReq;
|
||||
|
||||
public class PushTransport extends BaseTransport {
|
||||
|
||||
public static final int TYPE_MESSAGE_PLAINTEXT = 0;
|
||||
public static final int TYPE_MESSAGE_CIPHERTEXT = 1;
|
||||
public static final int TYPE_MESSAGE_KEY_EXCHANGE = 2;
|
||||
public static final int TYPE_MESSAGE_PREKEY_BUNDLE = 3;
|
||||
|
||||
private final Context context;
|
||||
private final MasterSecret masterSecret;
|
||||
|
||||
@@ -50,18 +59,15 @@ public class PushTransport extends BaseTransport {
|
||||
String recipientCanonicalNumber = PhoneNumberFormatter.formatNumber(recipient.getNumber(),
|
||||
localNumber);
|
||||
|
||||
// if (SessionRecord.hasSession(context, recipient)) {
|
||||
// byte[] cipherText = getEncryptedMessageForExistingSession(recipient, plaintext);
|
||||
// socket.sendMessage(recipientCanonicalNumber, new String(cipherText));
|
||||
// } else {
|
||||
// byte[] cipherText = getEncryptedMessageForNewSession(socket, recipient,
|
||||
// recipientCanonicalNumber,
|
||||
// plaintext);
|
||||
// socket.sendMessage(recipientCanonicalNumber, new String(cipherText));
|
||||
// }
|
||||
|
||||
|
||||
socket.sendMessage(recipientCanonicalNumber, message.getBody().getBody());
|
||||
if (SessionRecord.hasSession(context, recipient)) {
|
||||
byte[] cipherText = getEncryptedMessageForExistingSession(recipient, plaintext);
|
||||
socket.sendMessage(recipientCanonicalNumber, new String(cipherText), TYPE_MESSAGE_CIPHERTEXT);
|
||||
} else {
|
||||
byte[] cipherText = getEncryptedMessageForNewSession(socket, recipient,
|
||||
recipientCanonicalNumber,
|
||||
plaintext);
|
||||
socket.sendMessage(recipientCanonicalNumber, new String(cipherText), TYPE_MESSAGE_PREKEY_BUNDLE);
|
||||
}
|
||||
|
||||
context.sendBroadcast(constructSentIntent(context, message.getId(), message.getType()));
|
||||
} catch (RateLimitException e) {
|
||||
@@ -78,8 +84,8 @@ public class PushTransport extends BaseTransport {
|
||||
String messageText = PartParser.getMessageText(message.getBody());
|
||||
List<PushAttachmentData> attachments = getAttachmentsFromBody(message.getBody());
|
||||
|
||||
if (attachments.isEmpty()) socket.sendMessage(destinations, messageText);
|
||||
else socket.sendMessage(destinations, messageText, attachments);
|
||||
if (attachments.isEmpty()) socket.sendMessage(destinations, messageText, TYPE_MESSAGE_PLAINTEXT);
|
||||
else socket.sendMessage(destinations, messageText, attachments, TYPE_MESSAGE_PLAINTEXT);
|
||||
} catch (RateLimitException e) {
|
||||
Log.w("PushTransport", e);
|
||||
throw new IOException("Rate limit exceeded.");
|
||||
@@ -103,20 +109,29 @@ public class PushTransport extends BaseTransport {
|
||||
return attachments;
|
||||
}
|
||||
|
||||
private byte[] getEncryptedMessageForNewSession(PushServiceSocket socket, Recipient recipient, String canonicalRecipientNumber, String plaintext) throws IOException {
|
||||
PreKeyEntity preKey = socket.getPreKey(canonicalRecipientNumber);
|
||||
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient);
|
||||
private byte[] getEncryptedMessageForNewSession(PushServiceSocket socket, Recipient recipient,
|
||||
String canonicalRecipientNumber, String plaintext)
|
||||
throws IOException
|
||||
{
|
||||
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
||||
IdentityKey identityKey = identityKeyPair.getPublicKey();
|
||||
PreKeyEntity preKey = socket.getPreKey(canonicalRecipientNumber);
|
||||
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient);
|
||||
|
||||
processor.processKeyExchangeMessage(preKey);
|
||||
|
||||
return plaintext.getBytes();
|
||||
// synchronized (SessionCipher.CIPHER_LOCK) {
|
||||
// SessionCipher sessionCipher = new SessionCipher(context, masterSecret, recipient, new SmsTransportDetails());
|
||||
// return sessionCipher.encryptMessage(plaintext.getBytes());
|
||||
// }
|
||||
EncryptedMessage message = new EncryptedMessage(context, masterSecret, identityKeyPair, new RawTransportDetails());
|
||||
byte[] bundledMessage = message.encrypt(recipient, plaintext.getBytes());
|
||||
|
||||
PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(identityKey, bundledMessage);
|
||||
return preKeyBundleMessage.serialize().getBytes();
|
||||
}
|
||||
|
||||
private byte[] getEncryptedMessageForExistingSession(Recipient recipient, String plaintext) {
|
||||
EncryptedMessage message = new EncryptedMessage(context, masterSecret, new TextTransport());
|
||||
private byte[] getEncryptedMessageForExistingSession(Recipient recipient, String plaintext)
|
||||
throws IOException
|
||||
{
|
||||
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
||||
EncryptedMessage message = new EncryptedMessage(context, masterSecret, identityKeyPair, new TextTransport());
|
||||
return message.encrypt(recipient, plaintext.getBytes());
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import android.content.Context;
|
||||
import android.telephony.SmsManager;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
|
||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
import org.whispersystems.textsecure.crypto.SessionCipher;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
@@ -139,7 +141,8 @@ public class SmsTransport extends BaseTransport {
|
||||
}
|
||||
|
||||
private String getAsymmetricEncrypt(MasterSecret masterSecret, String body, Recipient recipient) {
|
||||
EncryptedMessage message = new EncryptedMessage(context, masterSecret, new SmsTransportDetails());
|
||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
||||
EncryptedMessage message = new EncryptedMessage(context, masterSecret, identityKey, new SmsTransportDetails());
|
||||
return new String(message.encrypt(recipient, body.getBytes()));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user