mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +00:00
Basic support for prekeybundle message delivery and receipt.
This commit is contained in:
parent
1cc2762656
commit
7f642666dd
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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 {
|
||||||
|
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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());
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isPreKeyBundle() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int describeContents() {
|
public int describeContents() {
|
||||||
return 0;
|
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.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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user