Basic support for prekeybundle message delivery and receipt.

This commit is contained in:
Moxie Marlinspike 2013-08-21 17:25:19 -07:00
parent 1cc2762656
commit 7f642666dd
31 changed files with 705 additions and 111 deletions

View File

@ -1,7 +1,7 @@
package org.whispersystems.textsecure; package org.whispersystems.textsecure;
public class Release { public class Release {
public static final String PUSH_SERVICE_URL = "https://gcm.textsecure.whispersystems.org"; // public static final String PUSH_SERVICE_URL = "https://gcm.textsecure.whispersystems.org";
// public static final String PUSH_SERVICE_URL = "http://192.168.1.135:8080"; public static final String PUSH_SERVICE_URL = "http://192.168.1.135:8080";
public static final boolean ENFORCE_SSL = true; public static final boolean ENFORCE_SSL = false;
} }

View File

@ -0,0 +1,43 @@
/**
* 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.crypto;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
/**
* Holder for public and private identity key pair.
*
* @author Moxie Marlinspike
*/
public class IdentityKeyPair {
private final IdentityKey publicKey;
private final ECPrivateKeyParameters privateKey;
public IdentityKeyPair(IdentityKey publicKey, ECPrivateKeyParameters privateKey) {
this.publicKey = publicKey;
this.privateKey = privateKey;
}
public IdentityKey getPublicKey() {
return publicKey;
}
public ECPrivateKeyParameters getPrivateKey() {
return privateKey;
}
}

View File

@ -14,7 +14,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.thoughtcrime.securesms.crypto; package org.whispersystems.textsecure.crypto;
public class InvalidVersionException extends Exception { public class InvalidVersionException extends Exception {

View File

@ -33,6 +33,10 @@ public class PreKeyPair {
return publicKey; return publicKey;
} }
public AsymmetricCipherKeyPair getKeyPair() {
return new AsymmetricCipherKeyPair(publicKey.getPublicKey(), privateKey);
}
public byte[] serialize() { public byte[] serialize() {
byte[] publicKeyBytes = publicKey.serialize(); byte[] publicKeyBytes = publicKey.serialize();
byte[] privateKeyBytes = masterCipher.encryptKey(privateKey); byte[] privateKeyBytes = masterCipher.encryptKey(privateKey);

View File

@ -42,6 +42,9 @@ import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
/** /**
* This is where the session encryption magic happens. Implements a compressed version of the OTR protocol. * This is where the session encryption magic happens. Implements a compressed version of the OTR protocol.
@ -58,35 +61,42 @@ public class SessionCipher {
public static final int ENCRYPTED_MESSAGE_OVERHEAD = EncryptedMessage.HEADER_LENGTH + MessageMac.MAC_LENGTH; public static final int ENCRYPTED_MESSAGE_OVERHEAD = EncryptedMessage.HEADER_LENGTH + MessageMac.MAC_LENGTH;
public SessionCipherContext getEncryptionContext(Context context, MasterSecret masterSecret, public SessionCipherContext getEncryptionContext(Context context,
MasterSecret masterSecret,
IdentityKeyPair localIdentityKey,
CanonicalRecipientAddress recipient) CanonicalRecipientAddress recipient)
{ {
try { try {
KeyRecords records = getKeyRecords(context, masterSecret, recipient); KeyRecords records = getKeyRecords(context, masterSecret, recipient);
int localKeyId = records.getLocalKeyRecord().getCurrentKeyPair().getId(); int localKeyId = records.getLocalKeyRecord().getCurrentKeyPair().getId();
int remoteKeyId = records.getRemoteKeyRecord().getCurrentRemoteKey().getId(); int remoteKeyId = records.getRemoteKeyRecord().getCurrentRemoteKey().getId();
SessionKey sessionKey = getSessionKey(masterSecret, Cipher.ENCRYPT_MODE, records, localKeyId, remoteKeyId); int negotiatedVersion = records.getSessionRecord().getSessionVersion();
SessionKey sessionKey = getSessionKey(masterSecret, Cipher.ENCRYPT_MODE, negotiatedVersion, localIdentityKey, records, localKeyId, remoteKeyId);
PublicKey nextKey = records.getLocalKeyRecord().getNextKeyPair().getPublicKey(); PublicKey nextKey = records.getLocalKeyRecord().getNextKeyPair().getPublicKey();
int counter = records.getSessionRecord().getCounter(); int counter = records.getSessionRecord().getCounter();
int negotiatedVersion = records.getSessionRecord().getSessionVersion();
return new SessionCipherContext(records, sessionKey, localKeyId, remoteKeyId, nextKey, counter, negotiatedVersion);
return new SessionCipherContext(records, sessionKey, localKeyId, remoteKeyId,
nextKey, counter, negotiatedVersion, negotiatedVersion);
} catch (InvalidKeyIdException e) { } catch (InvalidKeyIdException e) {
throw new IllegalArgumentException(e); throw new IllegalArgumentException(e);
} }
} }
public SessionCipherContext getDecryptionContext(Context context, MasterSecret masterSecret, public SessionCipherContext getDecryptionContext(Context context, MasterSecret masterSecret,
IdentityKeyPair localIdentityKey,
CanonicalRecipientAddress recipient, CanonicalRecipientAddress recipient,
int senderKeyId, int recipientKeyId, int senderKeyId, int recipientKeyId,
PublicKey nextKey, int counter, PublicKey nextKey, int counter,
int negotiatedVersion) int messageVersion, int negotiatedVersion)
throws InvalidMessageException throws InvalidMessageException
{ {
try { try {
KeyRecords records = getKeyRecords(context, masterSecret, recipient); KeyRecords records = getKeyRecords(context, masterSecret, recipient);
SessionKey sessionKey = getSessionKey(masterSecret, Cipher.DECRYPT_MODE, records, recipientKeyId, senderKeyId); SessionKey sessionKey = getSessionKey(masterSecret, Cipher.DECRYPT_MODE, messageVersion, localIdentityKey, records, recipientKeyId, senderKeyId);
return new SessionCipherContext(records, sessionKey, senderKeyId, recipientKeyId, nextKey, counter, negotiatedVersion); return new SessionCipherContext(records, sessionKey, senderKeyId,
recipientKeyId, nextKey, counter,
messageVersion, negotiatedVersion);
} catch (InvalidKeyIdException e) { } catch (InvalidKeyIdException e) {
throw new InvalidMessageException(e); throw new InvalidMessageException(e);
} }
@ -193,12 +203,12 @@ public class SessionCipher {
} }
} }
private SecretKeySpec deriveCipherSecret(int mode, BigInteger sharedSecret, private SecretKeySpec deriveCipherSecret(int mode, List<BigInteger> sharedSecret,
KeyRecords records, int localKeyId, KeyRecords records, int localKeyId,
int remoteKeyId) int remoteKeyId)
throws InvalidKeyIdException throws InvalidKeyIdException
{ {
byte[] sharedSecretBytes = sharedSecret.toByteArray(); byte[] sharedSecretBytes = concatenateSharedSecrets(sharedSecret);
byte[] derivedBytes = deriveBytes(sharedSecretBytes, 16 * 2); byte[] derivedBytes = deriveBytes(sharedSecretBytes, 16 * 2);
byte[] cipherSecret = new byte[16]; byte[] cipherSecret = new byte[16];
@ -214,11 +224,33 @@ public class SessionCipher {
return new SecretKeySpec(cipherSecret, "AES"); return new SecretKeySpec(cipherSecret, "AES");
} }
private byte[] concatenateSharedSecrets(List<BigInteger> sharedSecrets) {
int totalByteSize = 0;
List<byte[]> byteValues = new LinkedList<byte[]>();
for (BigInteger sharedSecret : sharedSecrets) {
byte[] byteValue = sharedSecret.toByteArray();
totalByteSize += byteValue.length;
byteValues.add(byteValue);
}
byte[] combined = new byte[totalByteSize];
int offset = 0;
for (byte[] byteValue : byteValues) {
System.arraycopy(byteValue, 0, combined, offset, byteValue.length);
offset += byteValue.length;
}
return combined;
}
private boolean isLowEnd(KeyRecords records, int localKeyId, int remoteKeyId) private boolean isLowEnd(KeyRecords records, int localKeyId, int remoteKeyId)
throws InvalidKeyIdException throws InvalidKeyIdException
{ {
ECPublicKeyParameters localPublic = records.getLocalKeyRecord().getKeyPairForId(localKeyId).getPublicKey().getKey(); ECPublicKeyParameters localPublic = records.getLocalKeyRecord().getKeyPairForId(localKeyId).getPublicKey().getKey();
ECPublicKeyParameters remotePublic = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey(); ECPublicKeyParameters remotePublic = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey();
BigInteger local = localPublic.getQ().getX().toBigInteger(); BigInteger local = localPublic.getQ().getX().toBigInteger();
BigInteger remote = remotePublic.getQ().getX().toBigInteger(); BigInteger remote = remotePublic.getQ().getX().toBigInteger();
@ -246,7 +278,10 @@ public class SessionCipher {
return md.digest(); return md.digest();
} }
private SessionKey getSessionKey(MasterSecret masterSecret, int mode, KeyRecords records, private SessionKey getSessionKey(MasterSecret masterSecret, int mode,
int messageVersion,
IdentityKeyPair localIdentityKey,
KeyRecords records,
int localKeyId, int remoteKeyId) int localKeyId, int remoteKeyId)
throws InvalidKeyIdException throws InvalidKeyIdException
{ {
@ -256,23 +291,41 @@ public class SessionCipher {
if (sessionKey != null) if (sessionKey != null)
return sessionKey; return sessionKey;
BigInteger sharedSecret = calculateSharedSecret(records, localKeyId, remoteKeyId); List<BigInteger> sharedSecret = calculateSharedSecret(messageVersion, localIdentityKey, records, localKeyId, remoteKeyId);
SecretKeySpec cipherKey = deriveCipherSecret(mode, sharedSecret, records, localKeyId, remoteKeyId); SecretKeySpec cipherKey = deriveCipherSecret(mode, sharedSecret, records, localKeyId, remoteKeyId);
SecretKeySpec macKey = deriveMacSecret(cipherKey); SecretKeySpec macKey = deriveMacSecret(cipherKey);
return new SessionKey(localKeyId, remoteKeyId, cipherKey, macKey, masterSecret); return new SessionKey(localKeyId, remoteKeyId, cipherKey, macKey, masterSecret);
} }
private BigInteger calculateSharedSecret(KeyRecords records, int localKeyId, int remoteKeyId) private List<BigInteger> calculateSharedSecret(int messageVersion,
IdentityKeyPair localIdentityKey,
KeyRecords records,
int localKeyId, int remoteKeyId)
throws InvalidKeyIdException throws InvalidKeyIdException
{ {
ECDHBasicAgreement agreement = new ECDHBasicAgreement(); KeyPair localKeyPair = records.getLocalKeyRecord().getKeyPairForId(localKeyId);
AsymmetricCipherKeyPair localKeyPair = records.getLocalKeyRecord().getKeyPairForId(localKeyId).getKeyPair();
ECPublicKeyParameters remoteKey = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey(); ECPublicKeyParameters remoteKey = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey();
IdentityKey remoteIdentityKey = records.getSessionRecord().getIdentityKey();
agreement.init(localKeyPair.getPrivate()); if (isInitiallyExchangedKeys(records, localKeyId, remoteKeyId) &&
messageVersion >= EncryptedMessage.CRADLE_AGREEMENT_VERSION)
{
return SharedSecretCalculator.calculateSharedSecret(localKeyPair, localIdentityKey,
remoteKey, remoteIdentityKey);
} else {
return SharedSecretCalculator.calculateSharedSecret(localKeyPair, remoteKey);
}
}
return KeyUtil.calculateAgreement(agreement, remoteKey); private boolean isInitiallyExchangedKeys(KeyRecords records, int localKeyId, int remoteKeyId)
throws InvalidKeyIdException
{
byte[] localFingerprint = records.getSessionRecord().getLocalFingerprint();
byte[] remoteFingerprint = records.getSessionRecord().getRemoteFingerprint();
return Arrays.equals(localFingerprint, records.getLocalKeyRecord().getKeyPairForId(localKeyId).getPublicKey().getFingerprintBytes()) &&
Arrays.equals(remoteFingerprint, records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getFingerprintBytes());
} }
private KeyRecords getKeyRecords(Context context, MasterSecret masterSecret, private KeyRecords getKeyRecords(Context context, MasterSecret masterSecret,
@ -285,6 +338,7 @@ public class SessionCipher {
} }
private static class KeyRecords { private static class KeyRecords {
private final LocalKeyRecord localKeyRecord; private final LocalKeyRecord localKeyRecord;
private final RemoteKeyRecord remoteKeyRecord; private final RemoteKeyRecord remoteKeyRecord;
private final SessionRecord sessionRecord; private final SessionRecord sessionRecord;
@ -309,6 +363,7 @@ public class SessionCipher {
} }
public static class SessionCipherContext { public static class SessionCipherContext {
private final LocalKeyRecord localKeyRecord; private final LocalKeyRecord localKeyRecord;
private final RemoteKeyRecord remoteKeyRecord; private final RemoteKeyRecord remoteKeyRecord;
private final SessionRecord sessionRecord; private final SessionRecord sessionRecord;
@ -317,6 +372,7 @@ public class SessionCipher {
private final int recipientKeyId; private final int recipientKeyId;
private final PublicKey nextKey; private final PublicKey nextKey;
private final int counter; private final int counter;
private final int messageVersion;
private final int negotiatedVersion; private final int negotiatedVersion;
public SessionCipherContext(KeyRecords records, public SessionCipherContext(KeyRecords records,
@ -325,6 +381,7 @@ public class SessionCipher {
int receiverKeyId, int receiverKeyId,
PublicKey nextKey, PublicKey nextKey,
int counter, int counter,
int messageVersion,
int negotiatedVersion) int negotiatedVersion)
{ {
this.localKeyRecord = records.getLocalKeyRecord(); this.localKeyRecord = records.getLocalKeyRecord();
@ -335,6 +392,7 @@ public class SessionCipher {
this.recipientKeyId = receiverKeyId; this.recipientKeyId = receiverKeyId;
this.nextKey = nextKey; this.nextKey = nextKey;
this.counter = counter; this.counter = counter;
this.messageVersion = messageVersion;
this.negotiatedVersion = negotiatedVersion; this.negotiatedVersion = negotiatedVersion;
} }
@ -373,6 +431,10 @@ public class SessionCipher {
public int getNegotiatedVersion() { public int getNegotiatedVersion() {
return negotiatedVersion; return negotiatedVersion;
} }
public int getMessageVersion() {
return messageVersion;
}
} }
} }

View File

@ -0,0 +1,68 @@
package org.whispersystems.textsecure.crypto;
import android.util.Log;
import org.spongycastle.crypto.CipherParameters;
import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import java.math.BigInteger;
import java.util.LinkedList;
import java.util.List;
public class SharedSecretCalculator {
public static List<BigInteger> calculateSharedSecret(KeyPair localKeyPair,
IdentityKeyPair localIdentityKeyPair,
ECPublicKeyParameters remoteKey,
IdentityKey remoteIdentityKey)
{
Log.w("SharedSecretCalculator", "Calculating shared secret with cradle agreement...");
List<BigInteger> results = new LinkedList<BigInteger>();
if (isLowEnd(localKeyPair.getPublicKey().getKey(), remoteKey)) {
results.add(calculateAgreement(localIdentityKeyPair.getPrivateKey(), remoteKey));
results.add(calculateAgreement(localKeyPair.getKeyPair().getPrivate(),
remoteIdentityKey.getPublicKeyParameters()));
} else {
results.add(calculateAgreement(localKeyPair.getKeyPair().getPrivate(),
remoteIdentityKey.getPublicKeyParameters()));
results.add(calculateAgreement(localIdentityKeyPair.getPrivateKey(), remoteKey));
}
results.add(calculateAgreement(localKeyPair.getKeyPair().getPrivate(), remoteKey));
return results;
}
public static List<BigInteger> calculateSharedSecret(KeyPair localKeyPair,
ECPublicKeyParameters remoteKey)
{
Log.w("SharedSecretCalculator", "Calculating shared secret with standard agreement...");
List<BigInteger> results = new LinkedList<BigInteger>();
results.add(calculateAgreement(localKeyPair.getKeyPair().getPrivate(), remoteKey));
return results;
}
private static BigInteger calculateAgreement(CipherParameters privateKey,
ECPublicKeyParameters publicKey)
{
ECDHBasicAgreement agreement = new ECDHBasicAgreement();
agreement.init(privateKey);
return KeyUtil.calculateAgreement(agreement, publicKey);
}
private static boolean isLowEnd(ECPublicKeyParameters localPublic,
ECPublicKeyParameters remotePublic)
{
BigInteger local = localPublic.getQ().getX().toBigInteger();
BigInteger remote = remotePublic.getQ().getX().toBigInteger();
return local.compareTo(remote) < 0;
}
}

View File

@ -17,16 +17,20 @@
package org.whispersystems.textsecure.crypto.protocol; package org.whispersystems.textsecure.crypto.protocol;
import android.content.Context; import android.content.Context;
import android.util.Log;
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.InvalidMessageException; import org.whispersystems.textsecure.crypto.InvalidMessageException;
import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.MessageMac;
import org.whispersystems.textsecure.crypto.PublicKey; import org.whispersystems.textsecure.crypto.PublicKey;
import org.whispersystems.textsecure.crypto.SessionCipher; import org.whispersystems.textsecure.crypto.SessionCipher;
import org.whispersystems.textsecure.crypto.SessionCipher.SessionCipherContext; import org.whispersystems.textsecure.crypto.SessionCipher.SessionCipherContext;
import org.whispersystems.textsecure.crypto.TransportDetails; import org.whispersystems.textsecure.crypto.TransportDetails;
import org.whispersystems.textsecure.storage.CanonicalRecipientAddress; import org.whispersystems.textsecure.storage.CanonicalRecipientAddress;
import org.whispersystems.textsecure.util.Conversions; import org.whispersystems.textsecure.util.Conversions;
import org.whispersystems.textsecure.util.Hex;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -39,29 +43,35 @@ import java.nio.ByteBuffer;
public class EncryptedMessage { public class EncryptedMessage {
public static final int SUPPORTED_VERSION = 1; public static final int SUPPORTED_VERSION = 2;
public static final int CRADLE_AGREEMENT_VERSION = 2;
private static final int VERSION_LENGTH = 1; 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;
private static final int NEXT_KEY_LENGTH = PublicKey.KEY_SIZE; 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;
private static final int VERSION_OFFSET = 0; 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;
private static final int RECEIVER_KEY_ID_OFFSET = SENDER_KEY_ID_OFFSET + SENDER_KEY_ID_LENGTH; static final int RECEIVER_KEY_ID_OFFSET = SENDER_KEY_ID_OFFSET + SENDER_KEY_ID_LENGTH;
private static final int NEXT_KEY_OFFSET = RECEIVER_KEY_ID_OFFSET + RECEIVER_KEY_ID_LENGTH; 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;
private final Context context; private final Context context;
private final MasterSecret masterSecret; private final MasterSecret masterSecret;
private final IdentityKeyPair localIdentityKey;
private final TransportDetails transportDetails; private final TransportDetails transportDetails;
public EncryptedMessage(Context context, MasterSecret masterSecret, TransportDetails transportDetails) { public EncryptedMessage(Context context, MasterSecret masterSecret,
IdentityKeyPair localIdentityKey,
TransportDetails transportDetails)
{
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
this.masterSecret = masterSecret; this.masterSecret = masterSecret;
this.localIdentityKey = localIdentityKey;
this.transportDetails = transportDetails; this.transportDetails = transportDetails;
} }
@ -69,7 +79,7 @@ public class EncryptedMessage {
synchronized (SessionCipher.CIPHER_LOCK) { synchronized (SessionCipher.CIPHER_LOCK) {
byte[] paddedBody = transportDetails.getPaddedMessageBody(plaintext); byte[] paddedBody = transportDetails.getPaddedMessageBody(plaintext);
SessionCipher sessionCipher = new SessionCipher(); SessionCipher sessionCipher = new SessionCipher();
SessionCipherContext sessionContext = sessionCipher.getEncryptionContext(context, masterSecret, recipient); SessionCipherContext sessionContext = sessionCipher.getEncryptionContext(context, masterSecret, localIdentityKey, recipient);
byte[] ciphertextBody = sessionCipher.encrypt(sessionContext, paddedBody); byte[] ciphertextBody = sessionCipher.encrypt(sessionContext, paddedBody);
byte[] formattedCiphertext = getFormattedCiphertext(sessionContext, ciphertextBody); byte[] formattedCiphertext = getFormattedCiphertext(sessionContext, ciphertextBody);
byte[] ciphertextMessage = sessionCipher.mac(sessionContext, formattedCiphertext); byte[] ciphertextMessage = sessionCipher.mac(sessionContext, formattedCiphertext);
@ -84,6 +94,11 @@ public class EncryptedMessage {
synchronized (SessionCipher.CIPHER_LOCK) { synchronized (SessionCipher.CIPHER_LOCK) {
try { try {
byte[] decodedMessage = transportDetails.getDecodedMessage(ciphertext); byte[] decodedMessage = transportDetails.getDecodedMessage(ciphertext);
if (decodedMessage.length <= HEADER_LENGTH) {
throw new InvalidMessageException("Message is shorter than headers");
}
int messageVersion = getMessageVersion(decodedMessage); int messageVersion = getMessageVersion(decodedMessage);
if (messageVersion > SUPPORTED_VERSION) { if (messageVersion > SUPPORTED_VERSION) {
@ -96,13 +111,16 @@ public class EncryptedMessage {
int counter = getCiphertextCounter(decodedMessage); int counter = getCiphertextCounter(decodedMessage);
byte[] ciphertextBody = getMessageBody(decodedMessage); byte[] ciphertextBody = getMessageBody(decodedMessage);
PublicKey nextRemoteKey = getNextRemoteKey(decodedMessage); PublicKey nextRemoteKey = getNextRemoteKey(decodedMessage);
int version = Math.min(supportedVersion, SUPPORTED_VERSION); int negotiatedVersion = Math.min(supportedVersion, SUPPORTED_VERSION);
SessionCipher sessionCipher = new SessionCipher(); SessionCipher sessionCipher = new SessionCipher();
SessionCipherContext sessionContext = sessionCipher.getDecryptionContext(context, masterSecret, SessionCipherContext sessionContext = sessionCipher.getDecryptionContext(context, masterSecret,
localIdentityKey,
recipient, senderKeyId, recipient, senderKeyId,
receiverKeyId, receiverKeyId,
nextRemoteKey, nextRemoteKey,
counter, version); counter,
messageVersion,
negotiatedVersion);
sessionCipher.verifyMac(sessionContext, decodedMessage); sessionCipher.verifyMac(sessionContext, decodedMessage);
@ -155,7 +173,7 @@ public class EncryptedMessage {
} }
private byte[] getMessageBody(byte[] message) { private byte[] getMessageBody(byte[] message) {
byte[] body = new byte[message.length - HEADER_LENGTH]; byte[] body = new byte[message.length - HEADER_LENGTH - MessageMac.MAC_LENGTH];
System.arraycopy(message, TEXT_OFFSET, body, 0, body.length); System.arraycopy(message, TEXT_OFFSET, body, 0, body.length);
return body; return body;

View File

@ -0,0 +1,125 @@
/**
* 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.crypto.protocol;
import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.InvalidVersionException;
import org.whispersystems.textsecure.crypto.PublicKey;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.Conversions;
import java.io.IOException;
/**
* Class responsible for parsing and constructing PreKeyBundle messages.
*
* The class takes an existing encrypted message and bundles in the necessary
* additional information for a prekeybundle, namely the addition of the local
* identity key.
*/
public class PreKeyBundleMessage {
private static final int VERSION_LENGTH = EncryptedMessage.VERSION_LENGTH;
private static final int IDENTITY_KEY_LENGTH = IdentityKey.SIZE;
public static final int HEADER_LENGTH = IDENTITY_KEY_LENGTH + EncryptedMessage.HEADER_LENGTH;
private static final int VERSION_OFFSET = EncryptedMessage.VERSION_OFFSET;
private static final int IDENTITY_KEY_OFFSET = VERSION_OFFSET + EncryptedMessage.VERSION_LENGTH;
private static final int PUBLIC_KEY_OFFSET = IDENTITY_KEY_LENGTH + EncryptedMessage.NEXT_KEY_OFFSET;
private static final int PREKEY_ID_OFFSET = IDENTITY_KEY_LENGTH + EncryptedMessage.RECEIVER_KEY_ID_OFFSET;
private final byte[] messageBytes;
private final int supportedVersion;
private final int messageVersion;
private final int preKeyId;
private final IdentityKey identityKey;
private final PublicKey publicKey;
private final byte[] bundledMessage;
public PreKeyBundleMessage(String message) throws InvalidKeyException, InvalidVersionException {
try {
this.messageBytes = Base64.decodeWithoutPadding(message);
this.messageVersion = Conversions.highBitsToInt(this.messageBytes[VERSION_OFFSET]);
if (messageVersion > EncryptedMessage.SUPPORTED_VERSION)
throw new InvalidVersionException("Key exchange with version: " + messageVersion +
" but we only support: " + EncryptedMessage.SUPPORTED_VERSION);
this.supportedVersion = Conversions.lowBitsToInt(messageBytes[VERSION_OFFSET]);
this.publicKey = new PublicKey(messageBytes, PUBLIC_KEY_OFFSET);
this.identityKey = new IdentityKey(messageBytes, IDENTITY_KEY_OFFSET);
this.preKeyId = Conversions.byteArrayToMedium(messageBytes, PREKEY_ID_OFFSET);
this.bundledMessage = new byte[messageBytes.length - IDENTITY_KEY_LENGTH];
this.bundledMessage[VERSION_OFFSET] = this.messageBytes[VERSION_OFFSET];
System.arraycopy(messageBytes, IDENTITY_KEY_OFFSET+IDENTITY_KEY_LENGTH, bundledMessage, VERSION_OFFSET+VERSION_LENGTH, bundledMessage.length-VERSION_LENGTH);
} catch (IOException e) {
throw new InvalidKeyException(e);
}
}
public PreKeyBundleMessage(IdentityKey identityKey, byte[] bundledMessage) {
try {
this.supportedVersion = EncryptedMessage.SUPPORTED_VERSION;
this.messageVersion = EncryptedMessage.SUPPORTED_VERSION;
this.identityKey = identityKey;
this.publicKey = new PublicKey(bundledMessage, EncryptedMessage.NEXT_KEY_OFFSET);
this.preKeyId = Conversions.byteArrayToMedium(bundledMessage, EncryptedMessage.RECEIVER_KEY_ID_OFFSET);
this.bundledMessage = bundledMessage;
this.messageBytes = new byte[IDENTITY_KEY_LENGTH + bundledMessage.length];
byte[] identityKeyBytes = identityKey.serialize();
messageBytes[VERSION_OFFSET] = bundledMessage[VERSION_OFFSET];
System.arraycopy(identityKeyBytes, 0, messageBytes, IDENTITY_KEY_OFFSET, identityKeyBytes.length);
System.arraycopy(bundledMessage, VERSION_OFFSET+VERSION_LENGTH, messageBytes, IDENTITY_KEY_OFFSET+IDENTITY_KEY_LENGTH, bundledMessage.length-VERSION_LENGTH);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
public String serialize() {
return Base64.encodeBytesWithoutPadding(this.messageBytes);
}
public int getSupportedVersion() {
return supportedVersion;
}
public int getMessageVersion() {
return messageVersion;
}
public IdentityKey getIdentityKey() {
return identityKey;
}
public PublicKey getPublicKey() {
return publicKey;
}
public String getBundledMessage() {
return Base64.encodeBytesWithoutPadding(bundledMessage);
}
public int getPreKeyId() {
return preKeyId;
}
}

View File

@ -20,6 +20,7 @@ public class IncomingPushMessage implements Parcelable {
} }
}; };
private int type;
private String source; private String source;
private List<String> destinations; private List<String> destinations;
private String messageText; private String messageText;
@ -27,8 +28,9 @@ public class IncomingPushMessage implements Parcelable {
private long timestamp; private long timestamp;
public IncomingPushMessage(String source, List<String> destinations, String messageText, public IncomingPushMessage(String source, List<String> destinations, String messageText,
List<PushAttachmentPointer> attachments, long timestamp) int type, List<PushAttachmentPointer> attachments, long timestamp)
{ {
this.type = type;
this.source = source; this.source = source;
this.destinations = destinations; this.destinations = destinations;
this.messageText = messageText; this.messageText = messageText;
@ -84,4 +86,8 @@ public class IncomingPushMessage implements Parcelable {
dest.writeList(attachments); dest.writeList(attachments);
dest.writeLong(timestamp); dest.writeLong(timestamp);
} }
public int getType() {
return type;
}
} }

View File

@ -5,29 +5,36 @@ import java.util.List;
public class OutgoingPushMessage { public class OutgoingPushMessage {
public static final int TYPE_MESSAGE = 1;
public static final int TYPE_PREKEYED_MESSAGE = 2;
private int type;
private List<String> destinations; private List<String> destinations;
private String messageText; private String messageText;
private List<PushAttachmentPointer> attachments; private List<PushAttachmentPointer> attachments;
public OutgoingPushMessage(String destination, String messageText) { public OutgoingPushMessage(String destination, String messageText, int type) {
this.destinations = new LinkedList<String>(); this.destinations = new LinkedList<String>();
this.attachments = new LinkedList<PushAttachmentPointer>(); this.attachments = new LinkedList<PushAttachmentPointer>();
this.messageText = messageText; this.messageText = messageText;
this.destinations.add(destination); this.destinations.add(destination);
this.type = type;
} }
public OutgoingPushMessage(List<String> destinations, String messageText) { public OutgoingPushMessage(List<String> destinations, String messageText, int type) {
this.destinations = destinations; this.destinations = destinations;
this.messageText = messageText; this.messageText = messageText;
this.attachments = new LinkedList<PushAttachmentPointer>(); this.attachments = new LinkedList<PushAttachmentPointer>();
this.type = type;
} }
public OutgoingPushMessage(List<String> destinations, String messageText, public OutgoingPushMessage(List<String> destinations, String messageText,
List<PushAttachmentPointer> attachments) List<PushAttachmentPointer> attachments, int type)
{ {
this.destinations = destinations; this.destinations = destinations;
this.messageText = messageText; this.messageText = messageText;
this.attachments = attachments; this.attachments = attachments;
this.type = type;
} }
public List<String> getDestinations() { public List<String> getDestinations() {
@ -42,4 +49,7 @@ public class OutgoingPushMessage {
return attachments; return attachments;
} }
public int getType() {
return type;
}
} }

View File

@ -78,26 +78,26 @@ public class PushServiceSocket {
makeRequest(REGISTER_GCM_PATH, "DELETE", null); makeRequest(REGISTER_GCM_PATH, "DELETE", null);
} }
public void sendMessage(String recipient, String messageText) public void sendMessage(String recipient, String messageText, int type)
throws IOException throws IOException
{ {
OutgoingPushMessage message = new OutgoingPushMessage(recipient, messageText); OutgoingPushMessage message = new OutgoingPushMessage(recipient, messageText, type);
sendMessage(message); sendMessage(message);
} }
public void sendMessage(List<String> recipients, String messageText) public void sendMessage(List<String> recipients, String messageText, int type)
throws IOException throws IOException
{ {
OutgoingPushMessage message = new OutgoingPushMessage(recipients, messageText); OutgoingPushMessage message = new OutgoingPushMessage(recipients, messageText, type);
sendMessage(message); sendMessage(message);
} }
public void sendMessage(List<String> recipients, String messageText, public void sendMessage(List<String> recipients, String messageText,
List<PushAttachmentData> attachments) List<PushAttachmentData> attachments, int type)
throws IOException throws IOException
{ {
List<PushAttachmentPointer> attachmentIds = sendAttachments(attachments); List<PushAttachmentPointer> attachmentIds = sendAttachments(attachments);
OutgoingPushMessage message = new OutgoingPushMessage(recipients, messageText, attachmentIds); OutgoingPushMessage message = new OutgoingPushMessage(recipients, messageText, attachmentIds, type);
sendMessage(message); sendMessage(message);
} }

View File

@ -31,7 +31,7 @@ import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
import org.whispersystems.textsecure.crypto.InvalidKeyException; 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.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MasterSecret;

View File

@ -194,7 +194,9 @@ public class RegistrationProgressActivity extends SherlockActivity {
intent.putExtra("master_secret", masterSecret); intent.putExtra("master_secret", masterSecret);
startService(intent); startService(intent);
} else { } else {
startActivity(new Intent(this, RegistrationActivity.class)); Intent intent = new Intent(this, RegistrationActivity.class);
intent.putExtra("master_secret", masterSecret);
startActivity(intent);
finish(); finish();
} }
} }
@ -408,6 +410,7 @@ public class RegistrationProgressActivity extends SherlockActivity {
shutdownService(); shutdownService();
Intent activityIntent = new Intent(RegistrationProgressActivity.this, RegistrationActivity.class); Intent activityIntent = new Intent(RegistrationProgressActivity.this, RegistrationActivity.class);
activityIntent.putExtra("master_secret", masterSecret);
startActivity(activityIntent); startActivity(activityIntent);
finish(); finish();
} }

View File

@ -34,8 +34,11 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException; import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.sms.SmsTransportDetails; 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.InvalidKeyException;
import org.whispersystems.textsecure.crypto.InvalidMessageException; import org.whispersystems.textsecure.crypto.InvalidMessageException;
import org.whispersystems.textsecure.crypto.InvalidVersionException;
import org.whispersystems.textsecure.crypto.SessionCipher; import org.whispersystems.textsecure.crypto.SessionCipher;
import org.whispersystems.textsecure.crypto.protocol.EncryptedMessage; import org.whispersystems.textsecure.crypto.protocol.EncryptedMessage;
import org.whispersystems.textsecure.util.Hex; import org.whispersystems.textsecure.util.Hex;
@ -195,7 +198,8 @@ public class DecryptingQueue {
synchronized (SessionCipher.CIPHER_LOCK) { synchronized (SessionCipher.CIPHER_LOCK) {
Log.w("DecryptingQueue", "Decrypting: " + Hex.toString(ciphertextPduBytes)); 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 { try {
plaintextPduBytes = message.decrypt(recipient, ciphertextPduBytes); plaintextPduBytes = message.decrypt(recipient, ciphertextPduBytes);
@ -275,7 +279,9 @@ public class DecryptingQueue {
return; 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())); plaintextBody = new String(message.decrypt(recipient, body.getBytes()));
} catch (InvalidMessageException e) { } catch (InvalidMessageException e) {
Log.w("DecryptionQueue", e); Log.w("DecryptionQueue", e);

View File

@ -31,6 +31,7 @@ import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters; import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.crypto.signers.ECDSASigner; import org.spongycastle.crypto.signers.ECDSASigner;
import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.KeyUtil; import org.whispersystems.textsecure.crypto.KeyUtil;
import org.whispersystems.textsecure.crypto.MasterCipher; import org.whispersystems.textsecure.crypto.MasterCipher;
@ -76,6 +77,22 @@ public class IdentityKeyUtil {
} }
} }
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) { public static String getFingerprint(Context context) {
if (!hasIdentityKey(context)) return null; if (!hasIdentityKey(context)) return null;

View File

@ -23,12 +23,16 @@ import android.util.Log;
import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage; import org.thoughtcrime.securesms.crypto.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.whispersystems.textsecure.crypto.IdentityKey; import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.KeyPair;
import org.whispersystems.textsecure.crypto.KeyUtil; import org.whispersystems.textsecure.crypto.KeyUtil;
import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.PublicKey; import org.whispersystems.textsecure.crypto.PublicKey;
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
import org.whispersystems.textsecure.crypto.protocol.EncryptedMessage; import org.whispersystems.textsecure.crypto.protocol.EncryptedMessage;
import org.whispersystems.textsecure.push.PreKeyEntity; import org.whispersystems.textsecure.push.PreKeyEntity;
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
import org.whispersystems.textsecure.storage.LocalKeyRecord; import org.whispersystems.textsecure.storage.LocalKeyRecord;
import org.whispersystems.textsecure.storage.PreKeyRecord;
import org.whispersystems.textsecure.storage.RemoteKeyRecord; import org.whispersystems.textsecure.storage.RemoteKeyRecord;
import org.whispersystems.textsecure.storage.SessionRecord; import org.whispersystems.textsecure.storage.SessionRecord;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
@ -72,6 +76,10 @@ public class KeyExchangeProcessor {
return isTrusted(message.getIdentityKey()); return isTrusted(message.getIdentityKey());
} }
public boolean isTrusted(PreKeyBundleMessage message) {
return isTrusted(message.getIdentityKey());
}
public boolean isTrusted(IdentityKey identityKey) { public boolean isTrusted(IdentityKey identityKey) {
return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret, recipient, return DatabaseFactory.getIdentityDatabase(context).isValidIdentity(masterSecret, recipient,
identityKey); identityKey);
@ -94,6 +102,40 @@ public class KeyExchangeProcessor {
(localKeyRecord.getCurrentKeyPair() != null && localKeyRecord.getCurrentKeyPair().getId() != responseKeyId); (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) { public void processKeyExchangeMessage(PreKeyEntity message) {
PublicKey remoteKey = new PublicKey(message.getKeyId(), message.getPublicKey()); PublicKey remoteKey = new PublicKey(message.getKeyId(), message.getPublicKey());
remoteKeyRecord.setCurrentRemoteKey(remoteKey); remoteKeyRecord.setCurrentRemoteKey(remoteKey);
@ -101,6 +143,8 @@ public class KeyExchangeProcessor {
remoteKeyRecord.save(); remoteKeyRecord.save();
localKeyRecord = KeyUtil.initializeRecordFor(recipient, context, masterSecret); localKeyRecord = KeyUtil.initializeRecordFor(recipient, context, masterSecret);
localKeyRecord.setNextKeyPair(localKeyRecord.getCurrentKeyPair());
localKeyRecord.save();
sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(), sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(),
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes()); remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());

View File

@ -20,7 +20,7 @@ import android.content.Context;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; 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.IdentityKey;
import org.whispersystems.textsecure.crypto.InvalidKeyException; import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.MasterSecret; import org.whispersystems.textsecure.crypto.MasterSecret;

View File

@ -30,6 +30,8 @@ public interface MmsSmsColumns {
protected static final long KEY_EXCHANGE_BIT = 0x8000; protected static final long KEY_EXCHANGE_BIT = 0x8000;
protected static final long KEY_EXCHANGE_STALE_BIT = 0x4000; protected static final long KEY_EXCHANGE_STALE_BIT = 0x4000;
protected static final long KEY_EXCHANGE_PROCESSED_BIT = 0x2000; 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 // Secure Message Information
protected static final long SECURE_MESSAGE_BIT = 0x800000; protected static final long SECURE_MESSAGE_BIT = 0x800000;
@ -81,6 +83,14 @@ public interface MmsSmsColumns {
return (type & KEY_EXCHANGE_PROCESSED_BIT) != 0; 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) { public static boolean isSymmetricEncryption(long type) {
return (type & ENCRYPTION_SYMMETRIC_BIT) != 0; return (type & ENCRYPTION_SYMMETRIC_BIT) != 0;
} }

View File

@ -237,6 +237,8 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
type |= Types.KEY_EXCHANGE_BIT; type |= Types.KEY_EXCHANGE_BIT;
if (((IncomingKeyExchangeMessage)message).isStale()) type |= Types.KEY_EXCHANGE_STALE_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).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()) { } else if (message.isSecureMessage()) {
type |= Types.SECURE_MESSAGE_BIT; type |= Types.SECURE_MESSAGE_BIT;
type |= Types.ENCRYPTION_REMOTE_BIT; type |= Types.ENCRYPTION_REMOTE_BIT;

View File

@ -103,6 +103,14 @@ public abstract class MessageRecord extends DisplayRecord {
return SmsDatabase.Types.isProcessedKeyExchange(type); return SmsDatabase.Types.isProcessedKeyExchange(type);
} }
public boolean isCorruptedKeyExchange() {
return SmsDatabase.Types.isCorruptedKeyExchange(type);
}
public boolean isInvalidVersionKeyExchange() {
return SmsDatabase.Types.isInvalidVersionKeyExchange(type);
}
public Recipient getIndividualRecipient() { public Recipient getIndividualRecipient() {
return individualRecipient; return individualRecipient;
} }

View File

@ -12,7 +12,6 @@ import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.push.IncomingPushMessage; import org.whispersystems.textsecure.push.IncomingPushMessage;
import org.whispersystems.textsecure.push.PushServiceSocket; import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.push.RateLimitException;
import org.whispersystems.textsecure.util.Util; import org.whispersystems.textsecure.util.Util;
import java.io.IOException; import java.io.IOException;
@ -73,6 +72,7 @@ public class GcmIntentService extends GCMBaseIntentService {
Intent receivedIntent = new Intent(context, SendReceiveService.class); Intent receivedIntent = new Intent(context, SendReceiveService.class);
receivedIntent.setAction(SendReceiveService.RECEIVE_SMS_ACTION); receivedIntent.setAction(SendReceiveService.RECEIVE_SMS_ACTION);
receivedIntent.putParcelableArrayListExtra("text_messages", messages); receivedIntent.putParcelableArrayListExtra("text_messages", messages);
receivedIntent.putExtra("push_type", message.getType());
context.startService(receivedIntent); context.startService(receivedIntent);
} }

View File

@ -47,6 +47,10 @@ public abstract class WirePrefix {
return verifyPrefix("?TSM", message); return verifyPrefix("?TSM", message);
} }
public static boolean isPreKeyBundle(String message) {
return verifyPrefix("?TSP", message);
}
public static String calculateKeyExchangePrefix(String message) { public static String calculateKeyExchangePrefix(String message) {
return calculatePrefix(("?TSK" + message).getBytes(), PREFIX_BYTES); return calculatePrefix(("?TSK" + message).getBytes(), PREFIX_BYTES);
} }
@ -55,6 +59,10 @@ public abstract class WirePrefix {
return calculatePrefix(("?TSM" + message).getBytes(), PREFIX_BYTES); 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) { private static boolean verifyPrefix(String prefixType, String message) {
if (message.length() <= PREFIX_SIZE) if (message.length() <= PREFIX_SIZE)
return false; return false;

View File

@ -22,8 +22,11 @@ import android.util.Log;
import android.util.Pair; import android.util.Pair;
import org.thoughtcrime.securesms.crypto.DecryptingQueue; 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.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.protocol.KeyExchangeMessage;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor;
import org.whispersystems.textsecure.crypto.MasterSecret; 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.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler; import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
import java.util.List; import java.util.List;
@ -51,12 +56,29 @@ public class SmsReceiver {
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) { private IncomingTextMessage assembleMessageFragments(List<IncomingTextMessage> messages) {
IncomingTextMessage message = new IncomingTextMessage(messages); IncomingTextMessage message = new IncomingTextMessage(messages);
if (WirePrefix.isEncryptedMessage(message.getMessageBody()) || if (WirePrefix.isEncryptedMessage(message.getMessageBody()) ||
WirePrefix.isKeyExchange(message.getMessageBody())) WirePrefix.isKeyExchange(message.getMessageBody()) ||
WirePrefix.isPreKeyBundle(message.getMessageBody()))
{ {
return multipartMessageHandler.processPotentialMultipartMessage(message); return multipartMessageHandler.processPotentialMultipartMessage(message);
} else { } 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, private Pair<Long, Long> storeKeyExchangeMessage(MasterSecret masterSecret,
IncomingKeyExchangeMessage message) IncomingKeyExchangeMessage message)
{ {
@ -114,8 +173,10 @@ public class SmsReceiver {
} }
} catch (InvalidVersionException e) { } catch (InvalidVersionException e) {
Log.w("SmsReceiver", e); Log.w("SmsReceiver", e);
message.setInvalidVersion(true);
} catch (InvalidKeyException e) { } catch (InvalidKeyException e) {
Log.w("SmsReceiver", e); Log.w("SmsReceiver", e);
message.setCorrupted(true);
} }
} }
@ -124,13 +185,19 @@ public class SmsReceiver {
private Pair<Long, Long> storeMessage(MasterSecret masterSecret, IncomingTextMessage message) { private Pair<Long, Long> storeMessage(MasterSecret masterSecret, IncomingTextMessage message) {
if (message.isSecureMessage()) return storeSecureMessage(masterSecret, message); if (message.isSecureMessage()) return storeSecureMessage(masterSecret, message);
else if (message.isPreKeyBundle()) return storePreKeyBundledMessage(masterSecret, (IncomingKeyExchangeMessage) message);
else if (message.isKeyExchange()) return storeKeyExchangeMessage(masterSecret, (IncomingKeyExchangeMessage) message); else if (message.isKeyExchange()) return storeKeyExchangeMessage(masterSecret, (IncomingKeyExchangeMessage) message);
else return storeStandardMessage(masterSecret, message); else return storeStandardMessage(masterSecret, message);
} }
private void handleReceiveMessage(MasterSecret masterSecret, Intent intent) { private void handleReceiveMessage(MasterSecret masterSecret, Intent intent) {
List<IncomingTextMessage> messagesList = intent.getExtras().getParcelableArrayList("text_messages"); 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) { if (message != null) {
Pair<Long, Long> messageAndThreadId = storeMessage(masterSecret, message); Pair<Long, Long> messageAndThreadId = storeMessage(masterSecret, message);

View File

@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.sms;
public class IncomingEncryptedMessage extends IncomingTextMessage { public class IncomingEncryptedMessage extends IncomingTextMessage {
IncomingEncryptedMessage(IncomingTextMessage base, String newBody) { public IncomingEncryptedMessage(IncomingTextMessage base, String newBody) {
super(base, newBody); super(base, newBody);
} }

View File

@ -4,13 +4,17 @@ public class IncomingKeyExchangeMessage extends IncomingTextMessage {
private boolean isStale; private boolean isStale;
private boolean isProcessed; private boolean isProcessed;
private boolean isCorrupted;
private boolean isInvalidVersion;
IncomingKeyExchangeMessage(IncomingTextMessage base, String newBody) { public IncomingKeyExchangeMessage(IncomingTextMessage base, String newBody) {
super(base, newBody); super(base, newBody);
if (base instanceof IncomingKeyExchangeMessage) { if (base instanceof IncomingKeyExchangeMessage) {
this.isStale = ((IncomingKeyExchangeMessage)base).isStale; this.isStale = ((IncomingKeyExchangeMessage)base).isStale;
this.isProcessed = ((IncomingKeyExchangeMessage)base).isProcessed; 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; 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 @Override
public boolean isKeyExchange() { public boolean isKeyExchange() {
return true; return true;

View File

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

View File

@ -126,6 +126,10 @@ public class IncomingTextMessage implements Parcelable {
return false; return false;
} }
public boolean isPreKeyBundle() {
return false;
}
@Override @Override
public int describeContents() { public int describeContents() {
return 0; return 0;

View 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;
}
}

View File

@ -5,6 +5,9 @@ import android.telephony.TelephonyManager;
import android.util.Log; import android.util.Log;
import android.util.Pair; 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.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipher; import org.whispersystems.textsecure.crypto.SessionCipher;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
@ -134,7 +137,8 @@ public class MmsTransport {
} }
private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipient, byte[] pduBytes) { 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); return message.encrypt(new Recipient(null, recipient, null, null), pduBytes);
} }

View File

@ -3,16 +3,20 @@ package org.thoughtcrime.securesms.transport;
import android.content.Context; import android.content.Context;
import android.util.Log; import android.util.Log;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.KeyExchangeProcessor; 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.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.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.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.crypto.protocol.EncryptedMessage;
import org.whispersystems.textsecure.push.PreKeyEntity; import org.whispersystems.textsecure.push.PreKeyEntity;
import org.whispersystems.textsecure.push.PushAttachmentData; import org.whispersystems.textsecure.push.PushAttachmentData;
@ -31,6 +35,11 @@ import ws.com.google.android.mms.pdu.SendReq;
public class PushTransport extends BaseTransport { 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 Context context;
private final MasterSecret masterSecret; private final MasterSecret masterSecret;
@ -50,18 +59,15 @@ public class PushTransport extends BaseTransport {
String recipientCanonicalNumber = PhoneNumberFormatter.formatNumber(recipient.getNumber(), String recipientCanonicalNumber = PhoneNumberFormatter.formatNumber(recipient.getNumber(),
localNumber); localNumber);
// if (SessionRecord.hasSession(context, recipient)) { if (SessionRecord.hasSession(context, recipient)) {
// byte[] cipherText = getEncryptedMessageForExistingSession(recipient, plaintext); byte[] cipherText = getEncryptedMessageForExistingSession(recipient, plaintext);
// socket.sendMessage(recipientCanonicalNumber, new String(cipherText)); socket.sendMessage(recipientCanonicalNumber, new String(cipherText), TYPE_MESSAGE_CIPHERTEXT);
// } else { } else {
// byte[] cipherText = getEncryptedMessageForNewSession(socket, recipient, byte[] cipherText = getEncryptedMessageForNewSession(socket, recipient,
// recipientCanonicalNumber, recipientCanonicalNumber,
// plaintext); plaintext);
// socket.sendMessage(recipientCanonicalNumber, new String(cipherText)); socket.sendMessage(recipientCanonicalNumber, new String(cipherText), TYPE_MESSAGE_PREKEY_BUNDLE);
// } }
socket.sendMessage(recipientCanonicalNumber, message.getBody().getBody());
context.sendBroadcast(constructSentIntent(context, message.getId(), message.getType())); context.sendBroadcast(constructSentIntent(context, message.getId(), message.getType()));
} catch (RateLimitException e) { } catch (RateLimitException e) {
@ -78,8 +84,8 @@ public class PushTransport extends BaseTransport {
String messageText = PartParser.getMessageText(message.getBody()); String messageText = PartParser.getMessageText(message.getBody());
List<PushAttachmentData> attachments = getAttachmentsFromBody(message.getBody()); List<PushAttachmentData> attachments = getAttachmentsFromBody(message.getBody());
if (attachments.isEmpty()) socket.sendMessage(destinations, messageText); if (attachments.isEmpty()) socket.sendMessage(destinations, messageText, TYPE_MESSAGE_PLAINTEXT);
else socket.sendMessage(destinations, messageText, attachments); else socket.sendMessage(destinations, messageText, attachments, TYPE_MESSAGE_PLAINTEXT);
} catch (RateLimitException e) { } catch (RateLimitException e) {
Log.w("PushTransport", e); Log.w("PushTransport", e);
throw new IOException("Rate limit exceeded."); throw new IOException("Rate limit exceeded.");
@ -103,20 +109,29 @@ public class PushTransport extends BaseTransport {
return attachments; return attachments;
} }
private byte[] getEncryptedMessageForNewSession(PushServiceSocket socket, Recipient recipient, String canonicalRecipientNumber, String plaintext) throws IOException { 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); PreKeyEntity preKey = socket.getPreKey(canonicalRecipientNumber);
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient); KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient);
processor.processKeyExchangeMessage(preKey); processor.processKeyExchangeMessage(preKey);
return plaintext.getBytes(); EncryptedMessage message = new EncryptedMessage(context, masterSecret, identityKeyPair, new RawTransportDetails());
// synchronized (SessionCipher.CIPHER_LOCK) { byte[] bundledMessage = message.encrypt(recipient, plaintext.getBytes());
// SessionCipher sessionCipher = new SessionCipher(context, masterSecret, recipient, new SmsTransportDetails());
// return sessionCipher.encryptMessage(plaintext.getBytes()); PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(identityKey, bundledMessage);
// } return preKeyBundleMessage.serialize().getBytes();
} }
private byte[] getEncryptedMessageForExistingSession(Recipient recipient, String plaintext) { private byte[] getEncryptedMessageForExistingSession(Recipient recipient, String plaintext)
EncryptedMessage message = new EncryptedMessage(context, masterSecret, new TextTransport()); throws IOException
{
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
EncryptedMessage message = new EncryptedMessage(context, masterSecret, identityKeyPair, new TextTransport());
return message.encrypt(recipient, plaintext.getBytes()); return message.encrypt(recipient, plaintext.getBytes());
} }

View File

@ -5,6 +5,8 @@ import android.content.Context;
import android.telephony.SmsManager; import android.telephony.SmsManager;
import android.util.Log; 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.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipher; import org.whispersystems.textsecure.crypto.SessionCipher;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord; 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) { 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())); return new String(message.encrypt(recipient, body.getBytes()));
} }
} }