mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-20 08:08:26 +00:00
Refactor relationship between SessionCipher and Message.
This commit is contained in:
parent
d1969412fb
commit
1cc2762656
@ -22,7 +22,7 @@ import android.util.Log;
|
|||||||
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
||||||
import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
|
import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
|
||||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
||||||
import org.whispersystems.textsecure.crypto.protocol.Message;
|
import org.whispersystems.textsecure.crypto.protocol.EncryptedMessage;
|
||||||
import org.whispersystems.textsecure.storage.CanonicalRecipientAddress;
|
import org.whispersystems.textsecure.storage.CanonicalRecipientAddress;
|
||||||
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
||||||
import org.whispersystems.textsecure.storage.LocalKeyRecord;
|
import org.whispersystems.textsecure.storage.LocalKeyRecord;
|
||||||
@ -37,7 +37,6 @@ import javax.crypto.IllegalBlockSizeException;
|
|||||||
import javax.crypto.NoSuchPaddingException;
|
import javax.crypto.NoSuchPaddingException;
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import java.io.IOException;
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
@ -57,79 +56,77 @@ public class SessionCipher {
|
|||||||
public static final int CIPHER_KEY_LENGTH = 16;
|
public static final int CIPHER_KEY_LENGTH = 16;
|
||||||
public static final int MAC_KEY_LENGTH = 20;
|
public static final int MAC_KEY_LENGTH = 20;
|
||||||
|
|
||||||
public static final int ENCRYPTED_MESSAGE_OVERHEAD = Message.HEADER_LENGTH + MessageMac.MAC_LENGTH;
|
public static final int ENCRYPTED_MESSAGE_OVERHEAD = EncryptedMessage.HEADER_LENGTH + MessageMac.MAC_LENGTH;
|
||||||
// public static final int ENCRYPTED_SINGLE_MESSAGE_BODY_MAX_SIZE = SmsTransportDetails.SINGLE_MESSAGE_MAX_BYTES - ENCRYPTED_MESSAGE_OVERHEAD;
|
|
||||||
|
|
||||||
private final LocalKeyRecord localRecord;
|
public SessionCipherContext getEncryptionContext(Context context, MasterSecret masterSecret,
|
||||||
private final RemoteKeyRecord remoteRecord;
|
CanonicalRecipientAddress recipient)
|
||||||
private final SessionRecord sessionRecord;
|
{
|
||||||
private final MasterSecret masterSecret;
|
try {
|
||||||
private final TransportDetails transportDetails;
|
KeyRecords records = getKeyRecords(context, masterSecret, recipient);
|
||||||
|
int localKeyId = records.getLocalKeyRecord().getCurrentKeyPair().getId();
|
||||||
|
int remoteKeyId = records.getRemoteKeyRecord().getCurrentRemoteKey().getId();
|
||||||
|
SessionKey sessionKey = getSessionKey(masterSecret, Cipher.ENCRYPT_MODE, records, localKeyId, remoteKeyId);
|
||||||
|
PublicKey nextKey = records.getLocalKeyRecord().getNextKeyPair().getPublicKey();
|
||||||
|
int counter = records.getSessionRecord().getCounter();
|
||||||
|
int negotiatedVersion = records.getSessionRecord().getSessionVersion();
|
||||||
|
|
||||||
public SessionCipher(Context context, MasterSecret masterSecret, CanonicalRecipientAddress recipient, TransportDetails transportDetails) {
|
return new SessionCipherContext(records, sessionKey, localKeyId, remoteKeyId, nextKey, counter, negotiatedVersion);
|
||||||
Log.w("SessionCipher", "Constructing session cipher...");
|
} catch (InvalidKeyIdException e) {
|
||||||
this.masterSecret = masterSecret;
|
throw new IllegalArgumentException(e);
|
||||||
this.localRecord = new LocalKeyRecord(context, masterSecret, recipient);
|
}
|
||||||
this.remoteRecord = new RemoteKeyRecord(context, recipient);
|
|
||||||
this.sessionRecord = new SessionRecord(context, masterSecret, recipient);
|
|
||||||
this.transportDetails = transportDetails;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] encryptMessage(byte[] messageText) {
|
public SessionCipherContext getDecryptionContext(Context context, MasterSecret masterSecret,
|
||||||
|
CanonicalRecipientAddress recipient,
|
||||||
|
int senderKeyId, int recipientKeyId,
|
||||||
|
PublicKey nextKey, int counter,
|
||||||
|
int negotiatedVersion)
|
||||||
|
throws InvalidMessageException
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
KeyRecords records = getKeyRecords(context, masterSecret, recipient);
|
||||||
|
SessionKey sessionKey = getSessionKey(masterSecret, Cipher.DECRYPT_MODE, records, recipientKeyId, senderKeyId);
|
||||||
|
return new SessionCipherContext(records, sessionKey, senderKeyId, recipientKeyId, nextKey, counter, negotiatedVersion);
|
||||||
|
} catch (InvalidKeyIdException e) {
|
||||||
|
throw new InvalidMessageException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] encrypt(SessionCipherContext context, byte[] paddedMessageBody) {
|
||||||
Log.w("SessionCipher", "Encrypting message...");
|
Log.w("SessionCipher", "Encrypting message...");
|
||||||
try {
|
try {
|
||||||
int localId = localRecord.getCurrentKeyPair().getId();
|
byte[]cipherText = getCiphertext(paddedMessageBody, context.getSessionKey().getCipherKey(), context.getSessionRecord().getCounter());
|
||||||
int remoteId = remoteRecord.getCurrentRemoteKey().getId();
|
|
||||||
SessionKey sessionKey = getSessionKey(Cipher.ENCRYPT_MODE, localId, remoteId);
|
|
||||||
byte[]paddedMessage = transportDetails.getPaddedMessageBody(messageText);
|
|
||||||
byte[]cipherText = getCiphertext(paddedMessage, sessionKey.getCipherKey());
|
|
||||||
byte[]message = buildMessageFromCiphertext(cipherText);
|
|
||||||
byte[]messageWithMac = MessageMac.buildMessageWithMac(message, sessionKey.getMacKey());
|
|
||||||
|
|
||||||
sessionRecord.setSessionKey(sessionKey);
|
context.getSessionRecord().setSessionKey(context.getSessionKey());
|
||||||
sessionRecord.incrementCounter();
|
context.getSessionRecord().incrementCounter();
|
||||||
sessionRecord.save();
|
context.getSessionRecord().save();
|
||||||
|
|
||||||
return transportDetails.encodeMessage(messageWithMac);
|
return cipherText;
|
||||||
} catch (IllegalBlockSizeException e) {
|
} catch (IllegalBlockSizeException e) {
|
||||||
throw new IllegalArgumentException(e);
|
throw new IllegalArgumentException(e);
|
||||||
} catch (BadPaddingException e) {
|
} catch (BadPaddingException e) {
|
||||||
throw new IllegalArgumentException(e);
|
throw new IllegalArgumentException(e);
|
||||||
} catch (InvalidKeyIdException e) {
|
|
||||||
throw new IllegalArgumentException(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] decrypt(SessionCipherContext context, byte[] decodedCiphertext)
|
||||||
public byte[] decryptMessage(byte[] messageText) throws InvalidMessageException {
|
throws InvalidMessageException
|
||||||
|
{
|
||||||
Log.w("SessionCipher", "Decrypting message...");
|
Log.w("SessionCipher", "Decrypting message...");
|
||||||
try {
|
try {
|
||||||
byte[] decodedMessage = transportDetails.decodeMessage(messageText);
|
byte[] plaintextWithPadding = getPlaintext(decodedCiphertext, context.getSessionKey().getCipherKey(), context.getCounter());
|
||||||
Message message = new Message(MessageMac.getMessageWithoutMac(decodedMessage));
|
|
||||||
SessionKey sessionKey = getSessionKey(Cipher.DECRYPT_MODE, message.getReceiverKeyId(), message.getSenderKeyId());
|
|
||||||
|
|
||||||
MessageMac.verifyMac(decodedMessage, sessionKey.getMacKey());
|
context.getRemoteKeyRecord().updateCurrentRemoteKey(context.getNextKey());
|
||||||
|
context.getRemoteKeyRecord().save();
|
||||||
|
|
||||||
byte[] plaintextWithPadding = getPlaintext(message.getMessageText(), sessionKey.getCipherKey(), message.getCounter());
|
context.getLocalKeyRecord().advanceKeyIfNecessary(context.getRecipientKeyId());
|
||||||
byte[] plaintext = transportDetails.stripPaddedMessage(plaintextWithPadding);
|
context.getLocalKeyRecord().save();
|
||||||
|
|
||||||
remoteRecord.updateCurrentRemoteKey(message.getNextKey());
|
context.getSessionRecord().setSessionKey(context.getSessionKey());
|
||||||
remoteRecord.save();
|
context.getSessionRecord().setSessionVersion(context.getNegotiatedVersion());
|
||||||
|
context.getSessionRecord().save();
|
||||||
|
|
||||||
localRecord.advanceKeyIfNecessary(message.getReceiverKeyId());
|
return plaintextWithPadding;
|
||||||
localRecord.save();
|
|
||||||
|
|
||||||
sessionRecord.setSessionKey(sessionKey);
|
|
||||||
sessionRecord.setSessionVersion(message.getHighestMutuallySupportedVersion());
|
|
||||||
sessionRecord.save();
|
|
||||||
|
|
||||||
return plaintext;
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new InvalidMessageException("Encoding Failure", e);
|
|
||||||
} catch (InvalidKeyIdException e) {
|
|
||||||
throw new InvalidMessageException("Bad Key ID", e);
|
|
||||||
} catch (InvalidMacException e) {
|
|
||||||
throw new InvalidMessageException("Bad MAC", e);
|
|
||||||
} catch (IllegalBlockSizeException e) {
|
} catch (IllegalBlockSizeException e) {
|
||||||
throw new InvalidMessageException("assert", e);
|
throw new InvalidMessageException("assert", e);
|
||||||
} catch (BadPaddingException e) {
|
} catch (BadPaddingException e) {
|
||||||
@ -137,6 +134,20 @@ public class SessionCipher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] mac(SessionCipherContext context, byte[] formattedCiphertext) {
|
||||||
|
return MessageMac.buildMessageWithMac(formattedCiphertext, context.getSessionKey().getMacKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void verifyMac(SessionCipherContext context, byte[] decodedCiphertext)
|
||||||
|
throws InvalidMessageException
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
MessageMac.verifyMac(decodedCiphertext, context.getSessionKey().getMacKey());
|
||||||
|
} catch (InvalidMacException e) {
|
||||||
|
throw new InvalidMessageException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private SecretKeySpec deriveMacSecret(SecretKeySpec key) {
|
private SecretKeySpec deriveMacSecret(SecretKeySpec key) {
|
||||||
try {
|
try {
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||||
@ -148,24 +159,13 @@ public class SessionCipher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildMessageFromCiphertext(byte[] cipherText) {
|
|
||||||
Message message = new Message(localRecord.getCurrentKeyPair().getId(),
|
|
||||||
remoteRecord.getCurrentRemoteKey().getId(),
|
|
||||||
localRecord.getNextKeyPair().getPublicKey(),
|
|
||||||
sessionRecord.getCounter(),
|
|
||||||
cipherText, sessionRecord.getSessionVersion(), Message.SUPPORTED_VERSION);
|
|
||||||
|
|
||||||
return message.serialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private byte[] getPlaintext(byte[] cipherText, SecretKeySpec key, int counter) throws IllegalBlockSizeException, BadPaddingException {
|
private byte[] getPlaintext(byte[] cipherText, SecretKeySpec key, int counter) throws IllegalBlockSizeException, BadPaddingException {
|
||||||
Cipher cipher = getCipher(Cipher.DECRYPT_MODE, key, counter);
|
Cipher cipher = getCipher(Cipher.DECRYPT_MODE, key, counter);
|
||||||
return cipher.doFinal(cipherText);
|
return cipher.doFinal(cipherText);
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] getCiphertext(byte[] message, SecretKeySpec key) throws IllegalBlockSizeException, BadPaddingException {
|
private byte[] getCiphertext(byte[] message, SecretKeySpec key, int counter) throws IllegalBlockSizeException, BadPaddingException {
|
||||||
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, key, sessionRecord.getCounter());
|
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, key, counter);
|
||||||
return cipher.doFinal(message);
|
return cipher.doFinal(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,12 +193,16 @@ public class SessionCipher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SecretKeySpec deriveCipherSecret(int mode, BigInteger sharedSecret, int localKeyId, int remoteKeyId) throws InvalidKeyIdException {
|
private SecretKeySpec deriveCipherSecret(int mode, BigInteger sharedSecret,
|
||||||
|
KeyRecords records, int localKeyId,
|
||||||
|
int remoteKeyId)
|
||||||
|
throws InvalidKeyIdException
|
||||||
|
{
|
||||||
byte[] sharedSecretBytes = sharedSecret.toByteArray();
|
byte[] sharedSecretBytes = sharedSecret.toByteArray();
|
||||||
byte[] derivedBytes = deriveBytes(sharedSecretBytes, 16 * 2);
|
byte[] derivedBytes = deriveBytes(sharedSecretBytes, 16 * 2);
|
||||||
byte[] cipherSecret = new byte[16];
|
byte[] cipherSecret = new byte[16];
|
||||||
|
|
||||||
boolean isLowEnd = isLowEnd(localKeyId, remoteKeyId);
|
boolean isLowEnd = isLowEnd(records, localKeyId, remoteKeyId);
|
||||||
isLowEnd = (mode == Cipher.ENCRYPT_MODE ? isLowEnd : !isLowEnd);
|
isLowEnd = (mode == Cipher.ENCRYPT_MODE ? isLowEnd : !isLowEnd);
|
||||||
|
|
||||||
if (isLowEnd) {
|
if (isLowEnd) {
|
||||||
@ -210,9 +214,11 @@ public class SessionCipher {
|
|||||||
return new SecretKeySpec(cipherSecret, "AES");
|
return new SecretKeySpec(cipherSecret, "AES");
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isLowEnd(int localKeyId, int remoteKeyId) throws InvalidKeyIdException {
|
private boolean isLowEnd(KeyRecords records, int localKeyId, int remoteKeyId)
|
||||||
ECPublicKeyParameters localPublic = (ECPublicKeyParameters)localRecord.getKeyPairForId(localKeyId).getPublicKey().getKey();
|
throws InvalidKeyIdException
|
||||||
ECPublicKeyParameters remotePublic = (ECPublicKeyParameters)remoteRecord.getKeyForId(remoteKeyId).getKey();
|
{
|
||||||
|
ECPublicKeyParameters localPublic = records.getLocalKeyRecord().getKeyPairForId(localKeyId).getPublicKey().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();
|
||||||
|
|
||||||
@ -240,26 +246,133 @@ public class SessionCipher {
|
|||||||
return md.digest();
|
return md.digest();
|
||||||
}
|
}
|
||||||
|
|
||||||
private SessionKey getSessionKey(int mode, int localKeyId, int remoteKeyId) throws InvalidKeyIdException {
|
private SessionKey getSessionKey(MasterSecret masterSecret, int mode, KeyRecords records,
|
||||||
|
int localKeyId, int remoteKeyId)
|
||||||
|
throws InvalidKeyIdException
|
||||||
|
{
|
||||||
Log.w("SessionCipher", "Getting session key for local: " + localKeyId + " remote: " + remoteKeyId);
|
Log.w("SessionCipher", "Getting session key for local: " + localKeyId + " remote: " + remoteKeyId);
|
||||||
SessionKey sessionKey = sessionRecord.getSessionKey(localKeyId, remoteKeyId);
|
SessionKey sessionKey = records.getSessionRecord().getSessionKey(localKeyId, remoteKeyId);
|
||||||
if (sessionKey != null) return sessionKey;
|
|
||||||
|
|
||||||
BigInteger sharedSecret = calculateSharedSecret(localKeyId, remoteKeyId);
|
if (sessionKey != null)
|
||||||
SecretKeySpec cipherKey = deriveCipherSecret(mode, sharedSecret, localKeyId, remoteKeyId);
|
return sessionKey;
|
||||||
|
|
||||||
|
BigInteger sharedSecret = calculateSharedSecret(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(int localKeyId, int remoteKeyId) throws InvalidKeyIdException {
|
private BigInteger calculateSharedSecret(KeyRecords records, int localKeyId, int remoteKeyId)
|
||||||
|
throws InvalidKeyIdException
|
||||||
|
{
|
||||||
ECDHBasicAgreement agreement = new ECDHBasicAgreement();
|
ECDHBasicAgreement agreement = new ECDHBasicAgreement();
|
||||||
AsymmetricCipherKeyPair localKeyPair = localRecord.getKeyPairForId(localKeyId).getKeyPair();
|
AsymmetricCipherKeyPair localKeyPair = records.getLocalKeyRecord().getKeyPairForId(localKeyId).getKeyPair();
|
||||||
ECPublicKeyParameters remoteKey = remoteRecord.getKeyForId(remoteKeyId).getKey();
|
ECPublicKeyParameters remoteKey = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey();
|
||||||
|
|
||||||
agreement.init(localKeyPair.getPrivate());
|
agreement.init(localKeyPair.getPrivate());
|
||||||
BigInteger secret = KeyUtil.calculateAgreement(agreement, remoteKey);
|
|
||||||
|
|
||||||
return secret;
|
return KeyUtil.calculateAgreement(agreement, remoteKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyRecords getKeyRecords(Context context, MasterSecret masterSecret,
|
||||||
|
CanonicalRecipientAddress recipient)
|
||||||
|
{
|
||||||
|
LocalKeyRecord localKeyRecord = new LocalKeyRecord(context, masterSecret, recipient);
|
||||||
|
RemoteKeyRecord remoteKeyRecord = new RemoteKeyRecord(context, recipient);
|
||||||
|
SessionRecord sessionRecord = new SessionRecord(context, masterSecret, recipient);
|
||||||
|
return new KeyRecords(localKeyRecord, remoteKeyRecord, sessionRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class KeyRecords {
|
||||||
|
private final LocalKeyRecord localKeyRecord;
|
||||||
|
private final RemoteKeyRecord remoteKeyRecord;
|
||||||
|
private final SessionRecord sessionRecord;
|
||||||
|
|
||||||
|
public KeyRecords(LocalKeyRecord localKeyRecord, RemoteKeyRecord remoteKeyRecord, SessionRecord sessionRecord) {
|
||||||
|
this.localKeyRecord = localKeyRecord;
|
||||||
|
this.remoteKeyRecord = remoteKeyRecord;
|
||||||
|
this.sessionRecord = sessionRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalKeyRecord getLocalKeyRecord() {
|
||||||
|
return localKeyRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RemoteKeyRecord getRemoteKeyRecord() {
|
||||||
|
return remoteKeyRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SessionRecord getSessionRecord() {
|
||||||
|
return sessionRecord;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class SessionCipherContext {
|
||||||
|
private final LocalKeyRecord localKeyRecord;
|
||||||
|
private final RemoteKeyRecord remoteKeyRecord;
|
||||||
|
private final SessionRecord sessionRecord;
|
||||||
|
private final SessionKey sessionKey;
|
||||||
|
private final int senderKeyId;
|
||||||
|
private final int recipientKeyId;
|
||||||
|
private final PublicKey nextKey;
|
||||||
|
private final int counter;
|
||||||
|
private final int negotiatedVersion;
|
||||||
|
|
||||||
|
public SessionCipherContext(KeyRecords records,
|
||||||
|
SessionKey sessionKey,
|
||||||
|
int senderKeyId,
|
||||||
|
int receiverKeyId,
|
||||||
|
PublicKey nextKey,
|
||||||
|
int counter,
|
||||||
|
int negotiatedVersion)
|
||||||
|
{
|
||||||
|
this.localKeyRecord = records.getLocalKeyRecord();
|
||||||
|
this.remoteKeyRecord = records.getRemoteKeyRecord();
|
||||||
|
this.sessionRecord = records.getSessionRecord();
|
||||||
|
this.sessionKey = sessionKey;
|
||||||
|
this.senderKeyId = senderKeyId;
|
||||||
|
this.recipientKeyId = receiverKeyId;
|
||||||
|
this.nextKey = nextKey;
|
||||||
|
this.counter = counter;
|
||||||
|
this.negotiatedVersion = negotiatedVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalKeyRecord getLocalKeyRecord() {
|
||||||
|
return localKeyRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemoteKeyRecord getRemoteKeyRecord() {
|
||||||
|
return remoteKeyRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SessionRecord getSessionRecord() {
|
||||||
|
return sessionRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SessionKey getSessionKey() {
|
||||||
|
return sessionKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PublicKey getNextKey() {
|
||||||
|
return nextKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCounter() {
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSenderKeyId() {
|
||||||
|
return senderKeyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRecipientKeyId() {
|
||||||
|
return recipientKeyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNegotiatedVersion() {
|
||||||
|
return negotiatedVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -20,9 +20,9 @@ import java.io.IOException;
|
|||||||
|
|
||||||
|
|
||||||
public interface TransportDetails {
|
public interface TransportDetails {
|
||||||
public byte[] stripPaddedMessage(byte[] messageWithPadding);
|
public byte[] getStrippedPaddingMessageBody(byte[] messageWithPadding);
|
||||||
public byte[] getPaddedMessageBody(byte[] messageBody);
|
public byte[] getPaddedMessageBody(byte[] messageBody);
|
||||||
|
|
||||||
public byte[] encodeMessage(byte[] messageWithMac);
|
public byte[] getEncodedMessage(byte[] messageWithMac);
|
||||||
public byte[] decodeMessage(byte[] encodedMessageBytes) throws IOException;
|
public byte[] getDecodedMessage(byte[] encodedMessageBytes) throws IOException;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,170 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2011 Whisper Systems
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.whispersystems.textsecure.crypto.protocol;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||||
|
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
||||||
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
|
import org.whispersystems.textsecure.crypto.PublicKey;
|
||||||
|
import org.whispersystems.textsecure.crypto.SessionCipher;
|
||||||
|
import org.whispersystems.textsecure.crypto.SessionCipher.SessionCipherContext;
|
||||||
|
import org.whispersystems.textsecure.crypto.TransportDetails;
|
||||||
|
import org.whispersystems.textsecure.storage.CanonicalRecipientAddress;
|
||||||
|
import org.whispersystems.textsecure.util.Conversions;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses and serializes the encrypted message format.
|
||||||
|
*
|
||||||
|
* @author Moxie Marlinspike
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class EncryptedMessage {
|
||||||
|
|
||||||
|
public static final int SUPPORTED_VERSION = 1;
|
||||||
|
|
||||||
|
private static final int VERSION_LENGTH = 1;
|
||||||
|
private static final int SENDER_KEY_ID_LENGTH = 3;
|
||||||
|
private static final int RECEIVER_KEY_ID_LENGTH = 3;
|
||||||
|
private static final int NEXT_KEY_LENGTH = PublicKey.KEY_SIZE;
|
||||||
|
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;
|
||||||
|
|
||||||
|
private static final int VERSION_OFFSET = 0;
|
||||||
|
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;
|
||||||
|
private 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 TEXT_OFFSET = COUNTER_OFFSET + COUNTER_LENGTH;
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final MasterSecret masterSecret;
|
||||||
|
private final TransportDetails transportDetails;
|
||||||
|
|
||||||
|
public EncryptedMessage(Context context, MasterSecret masterSecret, TransportDetails transportDetails) {
|
||||||
|
this.context = context.getApplicationContext();
|
||||||
|
this.masterSecret = masterSecret;
|
||||||
|
this.transportDetails = transportDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] encrypt(CanonicalRecipientAddress recipient, byte[] plaintext) {
|
||||||
|
synchronized (SessionCipher.CIPHER_LOCK) {
|
||||||
|
byte[] paddedBody = transportDetails.getPaddedMessageBody(plaintext);
|
||||||
|
SessionCipher sessionCipher = new SessionCipher();
|
||||||
|
SessionCipherContext sessionContext = sessionCipher.getEncryptionContext(context, masterSecret, recipient);
|
||||||
|
byte[] ciphertextBody = sessionCipher.encrypt(sessionContext, paddedBody);
|
||||||
|
byte[] formattedCiphertext = getFormattedCiphertext(sessionContext, ciphertextBody);
|
||||||
|
byte[] ciphertextMessage = sessionCipher.mac(sessionContext, formattedCiphertext);
|
||||||
|
|
||||||
|
return transportDetails.getEncodedMessage(ciphertextMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] decrypt(CanonicalRecipientAddress recipient, byte[] ciphertext)
|
||||||
|
throws InvalidMessageException
|
||||||
|
{
|
||||||
|
synchronized (SessionCipher.CIPHER_LOCK) {
|
||||||
|
try {
|
||||||
|
byte[] decodedMessage = transportDetails.getDecodedMessage(ciphertext);
|
||||||
|
int messageVersion = getMessageVersion(decodedMessage);
|
||||||
|
|
||||||
|
if (messageVersion > SUPPORTED_VERSION) {
|
||||||
|
throw new InvalidMessageException("Unsupported version: " + messageVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
int supportedVersion = getSupportedVersion(decodedMessage);
|
||||||
|
int receiverKeyId = getReceiverKeyId(decodedMessage);
|
||||||
|
int senderKeyId = getSenderKeyId(decodedMessage);
|
||||||
|
int counter = getCiphertextCounter(decodedMessage);
|
||||||
|
byte[] ciphertextBody = getMessageBody(decodedMessage);
|
||||||
|
PublicKey nextRemoteKey = getNextRemoteKey(decodedMessage);
|
||||||
|
int version = Math.min(supportedVersion, SUPPORTED_VERSION);
|
||||||
|
SessionCipher sessionCipher = new SessionCipher();
|
||||||
|
SessionCipherContext sessionContext = sessionCipher.getDecryptionContext(context, masterSecret,
|
||||||
|
recipient, senderKeyId,
|
||||||
|
receiverKeyId,
|
||||||
|
nextRemoteKey,
|
||||||
|
counter, version);
|
||||||
|
|
||||||
|
sessionCipher.verifyMac(sessionContext, decodedMessage);
|
||||||
|
|
||||||
|
byte[] plaintextWithPadding = sessionCipher.decrypt(sessionContext, ciphertextBody);
|
||||||
|
return transportDetails.getStrippedPaddingMessageBody(plaintextWithPadding);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new InvalidMessageException(e);
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
throw new InvalidMessageException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] getFormattedCiphertext(SessionCipherContext sessionContext, byte[] ciphertextBody) {
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(HEADER_LENGTH + ciphertextBody.length);
|
||||||
|
byte versionByte = Conversions.intsToByteHighAndLow(sessionContext.getNegotiatedVersion(), SUPPORTED_VERSION);
|
||||||
|
byte[] senderKeyIdBytes = Conversions.mediumToByteArray(sessionContext.getSenderKeyId());
|
||||||
|
byte[] receiverKeyIdBytes = Conversions.mediumToByteArray(sessionContext.getRecipientKeyId());
|
||||||
|
byte[] nextKeyBytes = sessionContext.getNextKey().serialize();
|
||||||
|
byte[] counterBytes = Conversions.mediumToByteArray(sessionContext.getCounter());
|
||||||
|
|
||||||
|
buffer.put(versionByte);
|
||||||
|
buffer.put(senderKeyIdBytes);
|
||||||
|
buffer.put(receiverKeyIdBytes);
|
||||||
|
buffer.put(nextKeyBytes);
|
||||||
|
buffer.put(counterBytes);
|
||||||
|
buffer.put(ciphertextBody);
|
||||||
|
|
||||||
|
return buffer.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getMessageVersion(byte[] message) {
|
||||||
|
return Conversions.highBitsToInt(message[VERSION_OFFSET]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getSupportedVersion(byte[] message) {
|
||||||
|
return Conversions.lowBitsToInt(message[VERSION_OFFSET]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getSenderKeyId(byte[] message) {
|
||||||
|
return Conversions.byteArrayToMedium(message, SENDER_KEY_ID_OFFSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getReceiverKeyId(byte[] message) {
|
||||||
|
return Conversions.byteArrayToMedium(message, RECEIVER_KEY_ID_OFFSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getCiphertextCounter(byte[] message) {
|
||||||
|
return Conversions.byteArrayToMedium(message, COUNTER_OFFSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] getMessageBody(byte[] message) {
|
||||||
|
byte[] body = new byte[message.length - HEADER_LENGTH];
|
||||||
|
System.arraycopy(message, TEXT_OFFSET, body, 0, body.length);
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PublicKey getNextRemoteKey(byte[] message) throws InvalidKeyException {
|
||||||
|
byte[] key = new byte[NEXT_KEY_LENGTH];
|
||||||
|
System.arraycopy(message, NEXT_KEY_OFFSET, key, 0, key.length);
|
||||||
|
|
||||||
|
return new PublicKey(key);
|
||||||
|
}
|
||||||
|
}
|
@ -1,151 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (C) 2011 Whisper Systems
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.whispersystems.textsecure.crypto.protocol;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
|
||||||
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
|
||||||
import org.whispersystems.textsecure.crypto.PublicKey;
|
|
||||||
import org.whispersystems.textsecure.util.Conversions;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses and serializes the encrypted message format.
|
|
||||||
*
|
|
||||||
* @author Moxie Marlinspike
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class Message {
|
|
||||||
|
|
||||||
public static final int SUPPORTED_VERSION = 1;
|
|
||||||
|
|
||||||
private static final int VERSION_LENGTH = 1;
|
|
||||||
private static final int SENDER_KEY_ID_LENGTH = 3;
|
|
||||||
private static final int RECEIVER_KEY_ID_LENGTH = 3;
|
|
||||||
private static final int NEXT_KEY_LENGTH = PublicKey.KEY_SIZE;
|
|
||||||
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;
|
|
||||||
|
|
||||||
private static final int VERSION_OFFSET = 0;
|
|
||||||
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;
|
|
||||||
private 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 TEXT_OFFSET = COUNTER_OFFSET + COUNTER_LENGTH;
|
|
||||||
|
|
||||||
private int senderKeyId;
|
|
||||||
private int receiverKeyId;
|
|
||||||
private int counter;
|
|
||||||
private int messageVersion;
|
|
||||||
private int supportedVersion;
|
|
||||||
private byte[] message;
|
|
||||||
|
|
||||||
private PublicKey nextKey;
|
|
||||||
|
|
||||||
public Message(int senderKeyId, int receiverKeyId, PublicKey nextKey, int counter, byte[] message, int messageVersion, int supportedVersion) {
|
|
||||||
this.senderKeyId = senderKeyId;
|
|
||||||
this.receiverKeyId = receiverKeyId;
|
|
||||||
this.nextKey = nextKey;
|
|
||||||
this.counter = counter;
|
|
||||||
this.message = message;
|
|
||||||
this.messageVersion = messageVersion;
|
|
||||||
this.supportedVersion = supportedVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Message(byte[] messageBytes) throws InvalidMessageException {
|
|
||||||
try {
|
|
||||||
if (messageBytes.length <= HEADER_LENGTH)
|
|
||||||
throw new InvalidMessageException("Message is shorter than headers.");
|
|
||||||
|
|
||||||
this.messageVersion = Conversions.highBitsToInt(messageBytes[VERSION_OFFSET]);
|
|
||||||
this.supportedVersion = Conversions.lowBitsToInt(messageBytes[VERSION_OFFSET]);
|
|
||||||
|
|
||||||
Log.w("Message", "Message Version: " + messageVersion);
|
|
||||||
Log.w("Message", "Supported Version: " + supportedVersion);
|
|
||||||
|
|
||||||
if (messageVersion > SUPPORTED_VERSION)
|
|
||||||
throw new InvalidMessageException("Message protocol version not supported: " + messageVersion);
|
|
||||||
|
|
||||||
this.senderKeyId = Conversions.byteArrayToMedium(messageBytes, SENDER_KEY_ID_OFFSET);
|
|
||||||
this.receiverKeyId = Conversions.byteArrayToMedium(messageBytes, RECEIVER_KEY_ID_OFFSET);
|
|
||||||
this.counter = Conversions.byteArrayToMedium(messageBytes, COUNTER_OFFSET);
|
|
||||||
|
|
||||||
Log.w("Message", "Parsed current version: " + messageVersion + " supported version: " + supportedVersion);
|
|
||||||
|
|
||||||
byte[] nextKeyBytes = new byte[NEXT_KEY_LENGTH];
|
|
||||||
byte[] textBytes = new byte[messageBytes.length - HEADER_LENGTH];
|
|
||||||
|
|
||||||
System.arraycopy(messageBytes, NEXT_KEY_OFFSET, nextKeyBytes, 0, nextKeyBytes.length);
|
|
||||||
System.arraycopy(messageBytes, TEXT_OFFSET, textBytes, 0, textBytes.length);
|
|
||||||
|
|
||||||
Log.w("Message", "Pulling next key out of message...");
|
|
||||||
this.nextKey = new PublicKey(nextKeyBytes);
|
|
||||||
this.message = textBytes;
|
|
||||||
} catch (InvalidKeyException ike) {
|
|
||||||
throw new AssertionError(ike);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] serialize() {
|
|
||||||
ByteBuffer buffer = ByteBuffer.allocate(HEADER_LENGTH + message.length);
|
|
||||||
|
|
||||||
Log.w("Message", "Constructing Message Version: (" + messageVersion + "," + supportedVersion + ")");
|
|
||||||
|
|
||||||
byte versionByte = Conversions.intsToByteHighAndLow(messageVersion, supportedVersion);
|
|
||||||
byte[] senderKeyIdBytes = Conversions.mediumToByteArray(senderKeyId);
|
|
||||||
byte[] receiverKeyIdBytes = Conversions.mediumToByteArray(receiverKeyId);
|
|
||||||
Log.w("Message", "Serializing next key into message...");
|
|
||||||
byte[] nextKeyBytes = nextKey.serialize();
|
|
||||||
byte[] counterBytes = Conversions.mediumToByteArray(counter);
|
|
||||||
|
|
||||||
buffer.put(versionByte);
|
|
||||||
buffer.put(senderKeyIdBytes);
|
|
||||||
buffer.put(receiverKeyIdBytes);
|
|
||||||
buffer.put(nextKeyBytes);
|
|
||||||
buffer.put(counterBytes);
|
|
||||||
buffer.put(message);
|
|
||||||
|
|
||||||
return buffer.array();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getHighestMutuallySupportedVersion() {
|
|
||||||
return Math.min(SUPPORTED_VERSION, this.supportedVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSenderKeyId() {
|
|
||||||
return this.senderKeyId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getReceiverKeyId() {
|
|
||||||
return this.receiverKeyId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PublicKey getNextKey() {
|
|
||||||
return this.nextKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCounter() {
|
|
||||||
return this.counter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getMessageText() {
|
|
||||||
return this.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -37,6 +37,7 @@ import org.thoughtcrime.securesms.sms.SmsTransportDetails;
|
|||||||
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.SessionCipher;
|
import org.whispersystems.textsecure.crypto.SessionCipher;
|
||||||
|
import org.whispersystems.textsecure.crypto.protocol.EncryptedMessage;
|
||||||
import org.whispersystems.textsecure.util.Hex;
|
import org.whispersystems.textsecure.util.Hex;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.WorkerThread;
|
import org.thoughtcrime.securesms.util.WorkerThread;
|
||||||
@ -194,9 +195,10 @@ 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));
|
||||||
SessionCipher cipher = new SessionCipher(context, masterSecret, recipient, new TextTransport());
|
EncryptedMessage message = new EncryptedMessage(context, masterSecret, new TextTransport());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
plaintextPduBytes = cipher.decryptMessage(ciphertextPduBytes);
|
plaintextPduBytes = message.decrypt(recipient, ciphertextPduBytes);
|
||||||
} catch (InvalidMessageException ime) {
|
} catch (InvalidMessageException ime) {
|
||||||
// XXX - For some reason, Sprint seems to append a single character to the
|
// XXX - For some reason, Sprint seems to append a single character to the
|
||||||
// end of message text segments. I don't know why, so here we just try
|
// end of message text segments. I don't know why, so here we just try
|
||||||
@ -205,7 +207,7 @@ public class DecryptingQueue {
|
|||||||
Log.w("DecryptingQueue", "Attempting truncated decrypt...");
|
Log.w("DecryptingQueue", "Attempting truncated decrypt...");
|
||||||
byte[] truncated = new byte[ciphertextPduBytes.length - 1];
|
byte[] truncated = new byte[ciphertextPduBytes.length - 1];
|
||||||
System.arraycopy(ciphertextPduBytes, 0, truncated, 0, truncated.length);
|
System.arraycopy(ciphertextPduBytes, 0, truncated, 0, truncated.length);
|
||||||
plaintextPduBytes = cipher.decryptMessage(truncated);
|
plaintextPduBytes = message.decrypt(recipient, truncated);
|
||||||
} else {
|
} else {
|
||||||
throw ime;
|
throw ime;
|
||||||
}
|
}
|
||||||
@ -273,8 +275,8 @@ public class DecryptingQueue {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SessionCipher cipher = new SessionCipher(context, masterSecret, recipient, new SmsTransportDetails());
|
EncryptedMessage message = new EncryptedMessage(context, masterSecret, new SmsTransportDetails());
|
||||||
plaintextBody = new String(cipher.decryptMessage(body.getBytes()));
|
plaintextBody = new String(message.decrypt(recipient, body.getBytes()));
|
||||||
} catch (InvalidMessageException e) {
|
} catch (InvalidMessageException e) {
|
||||||
Log.w("DecryptionQueue", e);
|
Log.w("DecryptionQueue", e);
|
||||||
database.markAsDecryptFailed(messageId);
|
database.markAsDecryptFailed(messageId);
|
||||||
|
@ -26,11 +26,11 @@ import org.whispersystems.textsecure.crypto.IdentityKey;
|
|||||||
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.EncryptedMessage;
|
||||||
import org.whispersystems.textsecure.push.PreKeyEntity;
|
import org.whispersystems.textsecure.push.PreKeyEntity;
|
||||||
import org.whispersystems.textsecure.storage.LocalKeyRecord;
|
import org.whispersystems.textsecure.storage.LocalKeyRecord;
|
||||||
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.whispersystems.textsecure.crypto.protocol.Message;
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||||
@ -105,7 +105,7 @@ public class KeyExchangeProcessor {
|
|||||||
sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(),
|
sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(),
|
||||||
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
|
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
|
||||||
sessionRecord.setIdentityKey(message.getIdentityKey());
|
sessionRecord.setIdentityKey(message.getIdentityKey());
|
||||||
sessionRecord.setSessionVersion(Message.SUPPORTED_VERSION);
|
sessionRecord.setSessionVersion(EncryptedMessage.SUPPORTED_VERSION);
|
||||||
sessionRecord.save();
|
sessionRecord.save();
|
||||||
|
|
||||||
DatabaseFactory.getIdentityDatabase(context)
|
DatabaseFactory.getIdentityDatabase(context)
|
||||||
@ -118,7 +118,7 @@ public class KeyExchangeProcessor {
|
|||||||
|
|
||||||
if (needsResponseFromUs()) {
|
if (needsResponseFromUs()) {
|
||||||
localKeyRecord = KeyUtil.initializeRecordFor(recipient, context, masterSecret);
|
localKeyRecord = KeyUtil.initializeRecordFor(recipient, context, masterSecret);
|
||||||
KeyExchangeMessage ourMessage = new KeyExchangeMessage(context, masterSecret, Math.min(Message.SUPPORTED_VERSION, message.getMaxVersion()), localKeyRecord, initiateKeyId);
|
KeyExchangeMessage ourMessage = new KeyExchangeMessage(context, masterSecret, Math.min(EncryptedMessage.SUPPORTED_VERSION, message.getMaxVersion()), localKeyRecord, initiateKeyId);
|
||||||
OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, ourMessage.serialize());
|
OutgoingKeyExchangeMessage textMessage = new OutgoingKeyExchangeMessage(recipient, ourMessage.serialize());
|
||||||
Log.w("KeyExchangeProcessor", "Responding with key exchange message fingerprint: " + ourMessage.getPublicKey().getFingerprint());
|
Log.w("KeyExchangeProcessor", "Responding with key exchange message fingerprint: " + ourMessage.getPublicKey().getFingerprint());
|
||||||
Log.w("KeyExchangeProcessor", "Which has a local key record fingerprint: " + localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprint());
|
Log.w("KeyExchangeProcessor", "Which has a local key record fingerprint: " + localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprint());
|
||||||
@ -132,9 +132,9 @@ public class KeyExchangeProcessor {
|
|||||||
sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(),
|
sessionRecord.setSessionId(localKeyRecord.getCurrentKeyPair().getPublicKey().getFingerprintBytes(),
|
||||||
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
|
remoteKeyRecord.getCurrentRemoteKey().getFingerprintBytes());
|
||||||
sessionRecord.setIdentityKey(message.getIdentityKey());
|
sessionRecord.setIdentityKey(message.getIdentityKey());
|
||||||
sessionRecord.setSessionVersion(Math.min(Message.SUPPORTED_VERSION, message.getMaxVersion()));
|
sessionRecord.setSessionVersion(Math.min(EncryptedMessage.SUPPORTED_VERSION, message.getMaxVersion()));
|
||||||
|
|
||||||
Log.w("KeyExchangeUtil", "Setting session version: " + Math.min(Message.SUPPORTED_VERSION, message.getMaxVersion()));
|
Log.w("KeyExchangeUtil", "Setting session version: " + Math.min(EncryptedMessage.SUPPORTED_VERSION, message.getMaxVersion()));
|
||||||
|
|
||||||
sessionRecord.save();
|
sessionRecord.save();
|
||||||
|
|
||||||
|
@ -25,8 +25,8 @@ 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;
|
||||||
import org.whispersystems.textsecure.crypto.PublicKey;
|
import org.whispersystems.textsecure.crypto.PublicKey;
|
||||||
|
import org.whispersystems.textsecure.crypto.protocol.EncryptedMessage;
|
||||||
import org.whispersystems.textsecure.storage.LocalKeyRecord;
|
import org.whispersystems.textsecure.storage.LocalKeyRecord;
|
||||||
import org.whispersystems.textsecure.crypto.protocol.Message;
|
|
||||||
import org.whispersystems.textsecure.util.Base64;
|
import org.whispersystems.textsecure.util.Base64;
|
||||||
import org.whispersystems.textsecure.util.Conversions;
|
import org.whispersystems.textsecure.util.Conversions;
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ public class KeyExchangeMessage {
|
|||||||
public KeyExchangeMessage(Context context, MasterSecret masterSecret, int messageVersion, LocalKeyRecord record, int highIdBits) {
|
public KeyExchangeMessage(Context context, MasterSecret masterSecret, int messageVersion, LocalKeyRecord record, int highIdBits) {
|
||||||
this.publicKey = new PublicKey(record.getCurrentKeyPair().getPublicKey());
|
this.publicKey = new PublicKey(record.getCurrentKeyPair().getPublicKey());
|
||||||
this.messageVersion = messageVersion;
|
this.messageVersion = messageVersion;
|
||||||
this.supportedVersion = Message.SUPPORTED_VERSION;
|
this.supportedVersion = EncryptedMessage.SUPPORTED_VERSION;
|
||||||
|
|
||||||
publicKey.setId(publicKey.getId() | (highIdBits << 12));
|
publicKey.setId(publicKey.getId() | (highIdBits << 12));
|
||||||
|
|
||||||
@ -93,9 +93,9 @@ public class KeyExchangeMessage {
|
|||||||
this.supportedVersion = Conversions.lowBitsToInt(keyBytes[0]);
|
this.supportedVersion = Conversions.lowBitsToInt(keyBytes[0]);
|
||||||
this.serialized = messageBody;
|
this.serialized = messageBody;
|
||||||
|
|
||||||
if (messageVersion > Message.SUPPORTED_VERSION)
|
if (messageVersion > EncryptedMessage.SUPPORTED_VERSION)
|
||||||
throw new InvalidVersionException("Key exchange with version: " + messageVersion +
|
throw new InvalidVersionException("Key exchange with version: " + messageVersion +
|
||||||
" but we only support: " + Message.SUPPORTED_VERSION);
|
" but we only support: " + EncryptedMessage.SUPPORTED_VERSION);
|
||||||
|
|
||||||
if (messageVersion >= 1)
|
if (messageVersion >= 1)
|
||||||
keyBytes = Base64.decodeWithoutPadding(messageBody);
|
keyBytes = Base64.decodeWithoutPadding(messageBody);
|
||||||
|
@ -1,207 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (C) 2011 Whisper Systems
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.thoughtcrime.securesms.mms;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.zip.CRC32;
|
|
||||||
import java.util.zip.DeflaterOutputStream;
|
|
||||||
import java.util.zip.InflaterInputStream;
|
|
||||||
|
|
||||||
import org.whispersystems.textsecure.crypto.SessionCipher;
|
|
||||||
import org.whispersystems.textsecure.crypto.TransportDetails;
|
|
||||||
import org.whispersystems.textsecure.util.Conversions;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
public class PngTransport implements TransportDetails {
|
|
||||||
|
|
||||||
private static final int CRC_LENGTH = 4;
|
|
||||||
private static final int IHDR_WIDTH_OFFSET = 8;
|
|
||||||
private static final int IHDR_HEIGHT_OFFSET = 12;
|
|
||||||
private static final int IDAT_HEADER_LENGTH = 8;
|
|
||||||
|
|
||||||
private static final byte[] PNG_HEADER = {(byte)0x89, (byte)0x50, (byte)0x4E, (byte)0x47, (byte)0x0D, (byte)0x0A, (byte)0x1A, (byte)0x0A};
|
|
||||||
private static final byte[] IHDR_CHUNK = {(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0D, (byte)0x49, (byte)0x48, (byte)0x44, (byte)0x52,
|
|
||||||
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
|
|
||||||
(byte)0x10, (byte)0x06, (byte)0x00, (byte)0x00, (byte)0x00};
|
|
||||||
private static final byte[] IEND_CHUNK = {(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x49, (byte)0x45, (byte)0x4E, (byte)0x44};
|
|
||||||
private static final byte[] IDAT_TAG = {(byte)0x49, (byte)0x44, (byte)0x41, (byte)0x54};
|
|
||||||
|
|
||||||
private static final int BYTES_PER_ROW = 640 * 8;
|
|
||||||
private static final int IDAT_LENGTH_OFFSET = 33;
|
|
||||||
|
|
||||||
private void readFully(InputStream in, byte[] buffer) throws IOException {
|
|
||||||
int totalRead = 0;
|
|
||||||
int read = 0;
|
|
||||||
|
|
||||||
while (totalRead < buffer.length) {
|
|
||||||
read = in.read(buffer, totalRead, buffer.length-totalRead);
|
|
||||||
Log.w("PngTransport", "Read: " + read);
|
|
||||||
if (read == -1)
|
|
||||||
throw new IOException("Could not fill buffer!");
|
|
||||||
totalRead+=read;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getImageHeight(byte[] data) {
|
|
||||||
int rows = (data.length / BYTES_PER_ROW);
|
|
||||||
assert(data.length % BYTES_PER_ROW == 0);
|
|
||||||
return rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] getIhdr(int width, int height) {
|
|
||||||
byte[] ihdr = new byte[IHDR_CHUNK.length];
|
|
||||||
System.arraycopy(IHDR_CHUNK, 0, ihdr, 0, ihdr.length);
|
|
||||||
Conversions.intToByteArray(ihdr, IHDR_WIDTH_OFFSET, width);
|
|
||||||
Conversions.intToByteArray(ihdr, IHDR_HEIGHT_OFFSET, height);
|
|
||||||
return ihdr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] calculateChunkCrc(byte[] header, byte[] chunk) {
|
|
||||||
byte[] crcBytes = new byte[4];
|
|
||||||
CRC32 crc = new CRC32();
|
|
||||||
crc.update(header, 4, header.length - 4);
|
|
||||||
|
|
||||||
if (chunk != null)
|
|
||||||
crc.update(chunk, 0, chunk.length);
|
|
||||||
|
|
||||||
long crcValue = crc.getValue();
|
|
||||||
Conversions.longTo4ByteArray(crcBytes, 0, crcValue);
|
|
||||||
|
|
||||||
return crcBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int readIhdr(ByteArrayInputStream bais) throws IOException {
|
|
||||||
byte[] ihdr = new byte[IHDR_CHUNK.length];
|
|
||||||
bais.read(ihdr);
|
|
||||||
return Conversions.byteArrayToInt(ihdr, IHDR_HEIGHT_OFFSET);
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] readIdatChunk(ByteArrayInputStream bais, int rows) throws IOException {
|
|
||||||
InflaterInputStream iis = new InflaterInputStream(bais);
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
byte[] buffer = new byte[BYTES_PER_ROW + 1];
|
|
||||||
|
|
||||||
Log.w("PngTransport", "Total image height: " + rows);
|
|
||||||
|
|
||||||
for (int i=0;i<rows;i++) {
|
|
||||||
readFully(iis, buffer);
|
|
||||||
Log.w("PngTransport", "Read row: " + i);
|
|
||||||
baos.write(buffer, 1, buffer.length-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return baos.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] getIdatChunkHeader(byte[] chunk) {
|
|
||||||
byte[] headerBytes = new byte[8];
|
|
||||||
Conversions.intToByteArray(headerBytes, 0, chunk.length);
|
|
||||||
System.arraycopy(IDAT_TAG, 0, headerBytes, 4, IDAT_TAG.length);
|
|
||||||
return headerBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] getIdatChunk(byte[] data) {
|
|
||||||
try {
|
|
||||||
int rows = (data.length / BYTES_PER_ROW);
|
|
||||||
|
|
||||||
assert(data.length % BYTES_PER_ROW == 0);
|
|
||||||
Log.w("PngTransport", "data % bytes_per_row: " + (data.length % BYTES_PER_ROW));
|
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream((rows * BYTES_PER_ROW) + rows);
|
|
||||||
DeflaterOutputStream dos = new DeflaterOutputStream(baos);
|
|
||||||
|
|
||||||
for (int i=0;i<rows;i++) {
|
|
||||||
dos.write((byte)0x00);
|
|
||||||
dos.write(data, (i*BYTES_PER_ROW), BYTES_PER_ROW);
|
|
||||||
}
|
|
||||||
|
|
||||||
dos.close();
|
|
||||||
|
|
||||||
return baos.toByteArray();
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
throw new AssertionError(ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] encodeMessage(byte[] data) {
|
|
||||||
try {
|
|
||||||
int height = getImageHeight(data);
|
|
||||||
byte[] idatChunk = getIdatChunk(data);
|
|
||||||
byte[] idatChunkHeader = getIdatChunkHeader(idatChunk);
|
|
||||||
byte[] ihdrChunk = getIhdr(640, height);
|
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(PNG_HEADER.length + IHDR_CHUNK.length + CRC_LENGTH +
|
|
||||||
idatChunkHeader.length + idatChunk.length + CRC_LENGTH +
|
|
||||||
IEND_CHUNK.length + CRC_LENGTH);
|
|
||||||
baos.write(PNG_HEADER);
|
|
||||||
baos.write(ihdrChunk);
|
|
||||||
baos.write(calculateChunkCrc(ihdrChunk, null));
|
|
||||||
baos.write(idatChunkHeader);
|
|
||||||
baos.write(idatChunk);
|
|
||||||
baos.write(calculateChunkCrc(idatChunkHeader, idatChunk));
|
|
||||||
baos.write(IEND_CHUNK);
|
|
||||||
baos.write(calculateChunkCrc(IEND_CHUNK, null));
|
|
||||||
|
|
||||||
return baos.toByteArray();
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
throw new AssertionError(ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] decodeMessage(byte[] encodedMessageBytes) throws IOException {
|
|
||||||
if (encodedMessageBytes.length < IDAT_LENGTH_OFFSET + 5)
|
|
||||||
throw new IOException("Encoded bytes too short!");
|
|
||||||
|
|
||||||
ByteArrayInputStream bais = new ByteArrayInputStream(encodedMessageBytes);
|
|
||||||
bais.skip(PNG_HEADER.length);
|
|
||||||
int rows = readIhdr(bais);
|
|
||||||
bais.skip(CRC_LENGTH);
|
|
||||||
bais.skip(IDAT_HEADER_LENGTH);
|
|
||||||
|
|
||||||
return readIdatChunk(bais, rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getPaddedMessageBody(byte[] messageBody) {
|
|
||||||
int rows = ((SessionCipher.ENCRYPTED_MESSAGE_OVERHEAD + messageBody.length) / BYTES_PER_ROW) + 1;
|
|
||||||
byte[] paddedMessage = new byte[(rows * BYTES_PER_ROW) - SessionCipher.ENCRYPTED_MESSAGE_OVERHEAD];
|
|
||||||
|
|
||||||
System.arraycopy(messageBody, 0, paddedMessage, 0, messageBody.length);
|
|
||||||
paddedMessage[messageBody.length] = (byte)0x01;
|
|
||||||
|
|
||||||
return paddedMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] stripPaddedMessage(byte[] messageWithPadding) {
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i=messageWithPadding.length-1;i>=0;i--) {
|
|
||||||
if (messageWithPadding[i] == (byte)0x01)
|
|
||||||
break;
|
|
||||||
else if (i == 0)
|
|
||||||
throw new AssertionError("No padding!");
|
|
||||||
}
|
|
||||||
|
|
||||||
int paddingLength = messageWithPadding.length - i;
|
|
||||||
byte[] message = new byte[messageWithPadding.length - paddingLength];
|
|
||||||
|
|
||||||
System.arraycopy(messageWithPadding, 0, message, 0, message.length);
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
}
|
|
@ -23,19 +23,23 @@ import org.whispersystems.textsecure.util.Base64;
|
|||||||
|
|
||||||
public class TextTransport implements TransportDetails {
|
public class TextTransport implements TransportDetails {
|
||||||
|
|
||||||
public byte[] decodeMessage(byte[] encodedMessageBytes) throws IOException {
|
@Override
|
||||||
|
public byte[] getDecodedMessage(byte[] encodedMessageBytes) throws IOException {
|
||||||
return Base64.decode(encodedMessageBytes);
|
return Base64.decode(encodedMessageBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] encodeMessage(byte[] messageWithMac) {
|
@Override
|
||||||
|
public byte[] getEncodedMessage(byte[] messageWithMac) {
|
||||||
return Base64.encodeBytes(messageWithMac).getBytes();
|
return Base64.encodeBytes(messageWithMac).getBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public byte[] getPaddedMessageBody(byte[] messageBody) {
|
public byte[] getPaddedMessageBody(byte[] messageBody) {
|
||||||
return messageBody;
|
return messageBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] stripPaddedMessage(byte[] messageWithPadding) {
|
@Override
|
||||||
|
public byte[] getStrippedPaddingMessageBody(byte[] messageWithPadding) {
|
||||||
return messageWithPadding;
|
return messageWithPadding;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,20 +38,20 @@ public class SmsTransportDetails implements TransportDetails {
|
|||||||
public static final int ENCRYPTED_SINGLE_MESSAGE_BODY_MAX_SIZE = SINGLE_MESSAGE_MAX_BYTES - SessionCipher.ENCRYPTED_MESSAGE_OVERHEAD;
|
public static final int ENCRYPTED_SINGLE_MESSAGE_BODY_MAX_SIZE = SINGLE_MESSAGE_MAX_BYTES - SessionCipher.ENCRYPTED_MESSAGE_OVERHEAD;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] encodeMessage(byte[] messageWithMac) {
|
public byte[] getEncodedMessage(byte[] messageWithMac) {
|
||||||
String encodedMessage = Base64.encodeBytesWithoutPadding(messageWithMac);
|
String encodedMessage = Base64.encodeBytesWithoutPadding(messageWithMac);
|
||||||
Log.w("SmsTransportDetails", "Encoded Message Length: " + encodedMessage.length());
|
Log.w("SmsTransportDetails", "Encoded Message Length: " + encodedMessage.length());
|
||||||
return encodedMessage.getBytes();
|
return encodedMessage.getBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] decodeMessage(byte[] encodedMessageBytes) throws IOException {
|
public byte[] getDecodedMessage(byte[] encodedMessageBytes) throws IOException {
|
||||||
String encodedMessage = new String(encodedMessageBytes);
|
String encodedMessage = new String(encodedMessageBytes);
|
||||||
return Base64.decodeWithoutPadding(encodedMessage);
|
return Base64.decodeWithoutPadding(encodedMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] stripPaddedMessage(byte[] messageWithPadding) {
|
public byte[] getStrippedPaddingMessageBody(byte[] messageWithPadding) {
|
||||||
int paddingBeginsIndex = 0;
|
int paddingBeginsIndex = 0;
|
||||||
|
|
||||||
for (int i=1;i<messageWithPadding.length;i++) {
|
for (int i=1;i<messageWithPadding.length;i++) {
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.transport;
|
|
||||||
|
|
||||||
import org.whispersystems.textsecure.crypto.TransportDetails;
|
|
||||||
import org.whispersystems.textsecure.util.Base64;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class BaseTransportDetails implements TransportDetails {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] stripPaddedMessage(byte[] messageWithPadding) {
|
|
||||||
int paddingBeginsIndex = 0;
|
|
||||||
|
|
||||||
for (int i=1;i<messageWithPadding.length;i++) {
|
|
||||||
if (messageWithPadding[i] == (byte)0x00) {
|
|
||||||
paddingBeginsIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (paddingBeginsIndex == 0)
|
|
||||||
return messageWithPadding;
|
|
||||||
|
|
||||||
byte[] message = new byte[paddingBeginsIndex];
|
|
||||||
System.arraycopy(messageWithPadding, 0, message, 0, message.length);
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getPaddedMessageBody(byte[] messageBody) {
|
|
||||||
return messageBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] encodeMessage(byte[] messageWithMac) {
|
|
||||||
return Base64.encodeBytesWithoutPadding(messageWithMac).getBytes();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] decodeMessage(byte[] encodedMessageBytes) throws IOException {
|
|
||||||
return Base64.decodeWithoutPadding(new String(encodedMessageBytes));
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.mms.MmsSendHelper;
|
|||||||
import org.thoughtcrime.securesms.mms.TextTransport;
|
import org.thoughtcrime.securesms.mms.TextTransport;
|
||||||
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.whispersystems.textsecure.crypto.protocol.EncryptedMessage;
|
||||||
import org.whispersystems.textsecure.util.Hex;
|
import org.whispersystems.textsecure.util.Hex;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -133,13 +134,8 @@ public class MmsTransport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipient, byte[] pduBytes) {
|
private byte[] getEncryptedPdu(MasterSecret masterSecret, String recipient, byte[] pduBytes) {
|
||||||
synchronized (SessionCipher.CIPHER_LOCK) {
|
EncryptedMessage message = new EncryptedMessage(context, masterSecret, new TextTransport());
|
||||||
SessionCipher cipher = new SessionCipher(context, masterSecret,
|
return message.encrypt(new Recipient(null, recipient, null, null), pduBytes);
|
||||||
new Recipient(null, recipient, null, null),
|
|
||||||
new TextTransport());
|
|
||||||
|
|
||||||
return cipher.encryptMessage(pduBytes);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isInconsistentResponse(SendReq message, SendConf response) {
|
private boolean isInconsistentResponse(SendReq message, SendConf response) {
|
||||||
|
@ -4,6 +4,7 @@ import android.content.Context;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
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.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
|
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
@ -12,6 +13,7 @@ import org.thoughtcrime.securesms.mms.PartParser;
|
|||||||
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.SessionCipher;
|
||||||
|
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;
|
||||||
import org.whispersystems.textsecure.push.PushServiceSocket;
|
import org.whispersystems.textsecure.push.PushServiceSocket;
|
||||||
@ -106,17 +108,16 @@ public class PushTransport extends BaseTransport {
|
|||||||
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient);
|
KeyExchangeProcessor processor = new KeyExchangeProcessor(context, masterSecret, recipient);
|
||||||
processor.processKeyExchangeMessage(preKey);
|
processor.processKeyExchangeMessage(preKey);
|
||||||
|
|
||||||
synchronized (SessionCipher.CIPHER_LOCK) {
|
return plaintext.getBytes();
|
||||||
SessionCipher sessionCipher = new SessionCipher(context, masterSecret, recipient, new SmsTransportDetails());
|
// synchronized (SessionCipher.CIPHER_LOCK) {
|
||||||
return sessionCipher.encryptMessage(plaintext.getBytes());
|
// SessionCipher sessionCipher = new SessionCipher(context, masterSecret, recipient, new SmsTransportDetails());
|
||||||
}
|
// return sessionCipher.encryptMessage(plaintext.getBytes());
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] getEncryptedMessageForExistingSession(Recipient recipient, String plaintext) {
|
private byte[] getEncryptedMessageForExistingSession(Recipient recipient, String plaintext) {
|
||||||
synchronized (SessionCipher.CIPHER_LOCK) {
|
EncryptedMessage message = new EncryptedMessage(context, masterSecret, new TextTransport());
|
||||||
SessionCipher sessionCipher = new SessionCipher(context, masterSecret, recipient, new SmsTransportDetails());
|
return message.encrypt(recipient, plaintext.getBytes());
|
||||||
return sessionCipher.encryptMessage(plaintext.getBytes());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler;
|
|||||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||||
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
|
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
import org.whispersystems.textsecure.crypto.protocol.EncryptedMessage;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
@ -138,9 +139,7 @@ public class SmsTransport extends BaseTransport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getAsymmetricEncrypt(MasterSecret masterSecret, String body, Recipient recipient) {
|
private String getAsymmetricEncrypt(MasterSecret masterSecret, String body, Recipient recipient) {
|
||||||
synchronized (SessionCipher.CIPHER_LOCK) {
|
EncryptedMessage message = new EncryptedMessage(context, masterSecret, new SmsTransportDetails());
|
||||||
SessionCipher cipher = new SessionCipher(context, masterSecret, recipient, new SmsTransportDetails());
|
return new String(message.encrypt(recipient, body.getBytes()));
|
||||||
return new String(cipher.encryptMessage(body.getBytes()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user