Refactor relationship between SessionCipher and Message.

This commit is contained in:
Moxie Marlinspike
2013-08-19 17:31:34 -07:00
parent d1969412fb
commit 1cc2762656
14 changed files with 420 additions and 537 deletions

View File

@@ -22,7 +22,7 @@ import android.util.Log;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
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.InvalidKeyIdException;
import org.whispersystems.textsecure.storage.LocalKeyRecord;
@@ -37,7 +37,6 @@ import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
@@ -57,85 +56,97 @@ public class SessionCipher {
public static final int CIPHER_KEY_LENGTH = 16;
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_SINGLE_MESSAGE_BODY_MAX_SIZE = SmsTransportDetails.SINGLE_MESSAGE_MAX_BYTES - ENCRYPTED_MESSAGE_OVERHEAD;
private final LocalKeyRecord localRecord;
private final RemoteKeyRecord remoteRecord;
private final SessionRecord sessionRecord;
private final MasterSecret masterSecret;
private final TransportDetails transportDetails;
public SessionCipher(Context context, MasterSecret masterSecret, CanonicalRecipientAddress recipient, TransportDetails transportDetails) {
Log.w("SessionCipher", "Constructing session cipher...");
this.masterSecret = masterSecret;
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) {
Log.w("SessionCipher", "Encrypting message...");
try {
int localId = localRecord.getCurrentKeyPair().getId();
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());
public static final int ENCRYPTED_MESSAGE_OVERHEAD = EncryptedMessage.HEADER_LENGTH + MessageMac.MAC_LENGTH;
sessionRecord.setSessionKey(sessionKey);
sessionRecord.incrementCounter();
sessionRecord.save();
return transportDetails.encodeMessage(messageWithMac);
} catch (IllegalBlockSizeException e) {
throw new IllegalArgumentException(e);
} catch (BadPaddingException e) {
throw new IllegalArgumentException(e);
public SessionCipherContext getEncryptionContext(Context context, MasterSecret masterSecret,
CanonicalRecipientAddress recipient)
{
try {
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();
return new SessionCipherContext(records, sessionKey, localKeyId, remoteKeyId, nextKey, counter, negotiatedVersion);
} catch (InvalidKeyIdException e) {
throw new IllegalArgumentException(e);
}
}
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...");
try {
byte[]cipherText = getCiphertext(paddedMessageBody, context.getSessionKey().getCipherKey(), context.getSessionRecord().getCounter());
context.getSessionRecord().setSessionKey(context.getSessionKey());
context.getSessionRecord().incrementCounter();
context.getSessionRecord().save();
return cipherText;
} catch (IllegalBlockSizeException e) {
throw new IllegalArgumentException(e);
} catch (BadPaddingException e) {
throw new IllegalArgumentException(e);
}
}
public byte[] decryptMessage(byte[] messageText) throws InvalidMessageException {
public byte[] decrypt(SessionCipherContext context, byte[] decodedCiphertext)
throws InvalidMessageException
{
Log.w("SessionCipher", "Decrypting message...");
try {
byte[] decodedMessage = transportDetails.decodeMessage(messageText);
Message message = new Message(MessageMac.getMessageWithoutMac(decodedMessage));
SessionKey sessionKey = getSessionKey(Cipher.DECRYPT_MODE, message.getReceiverKeyId(), message.getSenderKeyId());
byte[] plaintextWithPadding = getPlaintext(decodedCiphertext, context.getSessionKey().getCipherKey(), context.getCounter());
context.getRemoteKeyRecord().updateCurrentRemoteKey(context.getNextKey());
context.getRemoteKeyRecord().save();
MessageMac.verifyMac(decodedMessage, sessionKey.getMacKey());
context.getLocalKeyRecord().advanceKeyIfNecessary(context.getRecipientKeyId());
context.getLocalKeyRecord().save();
byte[] plaintextWithPadding = getPlaintext(message.getMessageText(), sessionKey.getCipherKey(), message.getCounter());
byte[] plaintext = transportDetails.stripPaddedMessage(plaintextWithPadding);
context.getSessionRecord().setSessionKey(context.getSessionKey());
context.getSessionRecord().setSessionVersion(context.getNegotiatedVersion());
context.getSessionRecord().save();
remoteRecord.updateCurrentRemoteKey(message.getNextKey());
remoteRecord.save();
localRecord.advanceKeyIfNecessary(message.getReceiverKeyId());
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);
return plaintextWithPadding;
} catch (IllegalBlockSizeException e) {
throw new InvalidMessageException("assert", e);
} catch (BadPaddingException e) {
throw new InvalidMessageException("assert", e);
}
}
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) {
try {
@@ -147,25 +158,14 @@ public class SessionCipher {
throw new IllegalArgumentException("SHA-1 Not Supported!",e);
}
}
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 {
Cipher cipher = getCipher(Cipher.DECRYPT_MODE, key, counter);
return cipher.doFinal(cipherText);
}
private byte[] getCiphertext(byte[] message, SecretKeySpec key) throws IllegalBlockSizeException, BadPaddingException {
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, key, sessionRecord.getCounter());
private byte[] getCiphertext(byte[] message, SecretKeySpec key, int counter) throws IllegalBlockSizeException, BadPaddingException {
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, key, counter);
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[] derivedBytes = deriveBytes(sharedSecretBytes, 16 * 2);
byte[] cipherSecret = new byte[16];
boolean isLowEnd = isLowEnd(localKeyId, remoteKeyId);
boolean isLowEnd = isLowEnd(records, localKeyId, remoteKeyId);
isLowEnd = (mode == Cipher.ENCRYPT_MODE ? isLowEnd : !isLowEnd);
if (isLowEnd) {
@@ -210,9 +214,11 @@ public class SessionCipher {
return new SecretKeySpec(cipherSecret, "AES");
}
private boolean isLowEnd(int localKeyId, int remoteKeyId) throws InvalidKeyIdException {
ECPublicKeyParameters localPublic = (ECPublicKeyParameters)localRecord.getKeyPairForId(localKeyId).getPublicKey().getKey();
ECPublicKeyParameters remotePublic = (ECPublicKeyParameters)remoteRecord.getKeyForId(remoteKeyId).getKey();
private boolean isLowEnd(KeyRecords records, int localKeyId, int remoteKeyId)
throws InvalidKeyIdException
{
ECPublicKeyParameters localPublic = records.getLocalKeyRecord().getKeyPairForId(localKeyId).getPublicKey().getKey();
ECPublicKeyParameters remotePublic = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey();
BigInteger local = localPublic.getQ().getX().toBigInteger();
BigInteger remote = remotePublic.getQ().getX().toBigInteger();
@@ -240,26 +246,133 @@ public class SessionCipher {
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);
SessionKey sessionKey = sessionRecord.getSessionKey(localKeyId, remoteKeyId);
if (sessionKey != null) return sessionKey;
SessionKey sessionKey = records.getSessionRecord().getSessionKey(localKeyId, remoteKeyId);
if (sessionKey != null)
return sessionKey;
BigInteger sharedSecret = calculateSharedSecret(localKeyId, remoteKeyId);
SecretKeySpec cipherKey = deriveCipherSecret(mode, sharedSecret, localKeyId, remoteKeyId);
BigInteger sharedSecret = calculateSharedSecret(records, localKeyId, remoteKeyId);
SecretKeySpec cipherKey = deriveCipherSecret(mode, sharedSecret, records, localKeyId, remoteKeyId);
SecretKeySpec macKey = deriveMacSecret(cipherKey);
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();
AsymmetricCipherKeyPair localKeyPair = localRecord.getKeyPairForId(localKeyId).getKeyPair();
ECPublicKeyParameters remoteKey = remoteRecord.getKeyForId(remoteKeyId).getKey();
AsymmetricCipherKeyPair localKeyPair = records.getLocalKeyRecord().getKeyPairForId(localKeyId).getKeyPair();
ECPublicKeyParameters remoteKey = records.getRemoteKeyRecord().getKeyForId(remoteKeyId).getKey();
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;
}
}
}

View File

@@ -20,9 +20,9 @@ import java.io.IOException;
public interface TransportDetails {
public byte[] stripPaddedMessage(byte[] messageWithPadding);
public byte[] getStrippedPaddingMessageBody(byte[] messageWithPadding);
public byte[] getPaddedMessageBody(byte[] messageBody);
public byte[] encodeMessage(byte[] messageWithMac);
public byte[] decodeMessage(byte[] encodedMessageBytes) throws IOException;
public byte[] getEncodedMessage(byte[] messageWithMac);
public byte[] getDecodedMessage(byte[] encodedMessageBytes) throws IOException;
}

View File

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

View File

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