mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-21 19:37:52 +00:00
Refactor the ciphertext message parsing and building.
This commit is contained in:
@@ -19,11 +19,8 @@ package org.whispersystems.textsecure.crypto;
|
||||
import android.content.Context;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.SessionCipher.SessionCipherContext;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
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.
|
||||
@@ -33,48 +30,23 @@ import java.nio.ByteBuffer;
|
||||
|
||||
public class MessageCipher {
|
||||
|
||||
public static final int SUPPORTED_VERSION = 2;
|
||||
public static final int CRADLE_AGREEMENT_VERSION = 2;
|
||||
|
||||
public static final int VERSION_LENGTH = 1;
|
||||
private static final int SENDER_KEY_ID_LENGTH = 3;
|
||||
private static final int RECEIVER_KEY_ID_LENGTH = 3;
|
||||
public 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;
|
||||
|
||||
public static final int VERSION_OFFSET = 0;
|
||||
private static final int SENDER_KEY_ID_OFFSET = VERSION_OFFSET + VERSION_LENGTH;
|
||||
public static final int RECEIVER_KEY_ID_OFFSET = SENDER_KEY_ID_OFFSET + SENDER_KEY_ID_LENGTH;
|
||||
public 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 IdentityKeyPair localIdentityKey;
|
||||
private final TransportDetails transportDetails;
|
||||
|
||||
public MessageCipher(Context context, MasterSecret masterSecret,
|
||||
IdentityKeyPair localIdentityKey,
|
||||
TransportDetails transportDetails)
|
||||
{
|
||||
public MessageCipher(Context context, MasterSecret masterSecret, IdentityKeyPair localIdentityKey) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.masterSecret = masterSecret;
|
||||
this.localIdentityKey = localIdentityKey;
|
||||
this.transportDetails = transportDetails;
|
||||
}
|
||||
|
||||
public byte[] encrypt(CanonicalRecipientAddress recipient, byte[] plaintext) {
|
||||
public CiphertextMessage encrypt(CanonicalRecipientAddress recipient, byte[] paddedBody) {
|
||||
synchronized (SessionCipher.CIPHER_LOCK) {
|
||||
byte[] paddedBody = transportDetails.getPaddedMessageBody(plaintext);
|
||||
SessionCipher sessionCipher = new SessionCipher();
|
||||
SessionCipherContext sessionContext = sessionCipher.getEncryptionContext(context, masterSecret, localIdentityKey, recipient);
|
||||
byte[] ciphertextBody = sessionCipher.encrypt(sessionContext, paddedBody);
|
||||
byte[] formattedCiphertext = getFormattedCiphertext(sessionContext, ciphertextBody);
|
||||
byte[] ciphertextMessage = sessionCipher.mac(sessionContext, formattedCiphertext);
|
||||
SessionCipher sessionCipher = new SessionCipher();
|
||||
SessionCipherContext sessionContext = sessionCipher.getEncryptionContext(context, masterSecret, localIdentityKey, recipient);
|
||||
byte[] ciphertextBody = sessionCipher.encrypt(sessionContext, paddedBody);
|
||||
|
||||
return transportDetails.getEncodedMessage(ciphertextMessage);
|
||||
return new CiphertextMessage(sessionContext, ciphertextBody);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,25 +55,17 @@ public class MessageCipher {
|
||||
{
|
||||
synchronized (SessionCipher.CIPHER_LOCK) {
|
||||
try {
|
||||
byte[] decodedMessage = transportDetails.getDecodedMessage(ciphertext);
|
||||
CiphertextMessage message = new CiphertextMessage(ciphertext);
|
||||
|
||||
if (decodedMessage.length <= HEADER_LENGTH) {
|
||||
throw new InvalidMessageException("Message is shorter than headers");
|
||||
}
|
||||
int messageVersion = message.getCurrentVersion();
|
||||
int supportedVersion = message.getSupportedVersion();
|
||||
int negotiatedVersion = Math.min(supportedVersion, CiphertextMessage.SUPPORTED_VERSION);
|
||||
int senderKeyId = message.getSenderKeyId();
|
||||
int receiverKeyId = message.getReceiverKeyId();
|
||||
PublicKey nextRemoteKey = new PublicKey(message.getNextKeyBytes());
|
||||
int counter = message.getCounter();
|
||||
byte[] body = message.getBody();
|
||||
|
||||
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 negotiatedVersion = Math.min(supportedVersion, SUPPORTED_VERSION);
|
||||
SessionCipher sessionCipher = new SessionCipher();
|
||||
SessionCipherContext sessionContext = sessionCipher.getDecryptionContext(context, masterSecret,
|
||||
localIdentityKey,
|
||||
@@ -112,67 +76,13 @@ public class MessageCipher {
|
||||
messageVersion,
|
||||
negotiatedVersion);
|
||||
|
||||
sessionCipher.verifyMac(sessionContext, decodedMessage);
|
||||
message.verifyMac(sessionContext);
|
||||
|
||||
byte[] plaintextWithPadding = sessionCipher.decrypt(sessionContext, ciphertextBody);
|
||||
return transportDetails.getStrippedPaddingMessageBody(plaintextWithPadding);
|
||||
} catch (IOException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
return sessionCipher.decrypt(sessionContext, body);
|
||||
} 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 - MessageMac.MAC_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);
|
||||
}
|
||||
}
|
||||
|
@@ -31,7 +31,7 @@ public class MessageMac {
|
||||
|
||||
public static final int MAC_LENGTH = 10;
|
||||
|
||||
private static byte[] calculateMac(byte[] message, int offset, int length, SecretKeySpec macKey) {
|
||||
public static byte[] calculateMac(byte[] message, int offset, int length, SecretKeySpec macKey) {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA1");
|
||||
mac.init(macKey);
|
||||
@@ -50,36 +50,19 @@ public class MessageMac {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] buildMessageWithMac(byte[] message, SecretKeySpec macKey) {
|
||||
byte[] macBytes = calculateMac(message, 0, message.length, macKey);
|
||||
byte[] combined = new byte[macBytes.length + message.length];
|
||||
System.arraycopy(message, 0, combined, 0, message.length);
|
||||
System.arraycopy(macBytes, 0, combined, message.length, macBytes.length);
|
||||
|
||||
public static void verifyMac(byte[] message, int offset, int length,
|
||||
byte[] receivedMac, SecretKeySpec macKey)
|
||||
throws InvalidMacException
|
||||
{
|
||||
byte[] localMac = calculateMac(message, offset, length, macKey);
|
||||
|
||||
Log.w("MessageMac", "Local Mac: " + Hex.toString(localMac));
|
||||
Log.w("MessageMac", "Remot Mac: " + Hex.toString(receivedMac));
|
||||
|
||||
return combined;
|
||||
}
|
||||
|
||||
public static byte[] getMessageWithoutMac(byte[] message) throws InvalidMacException {
|
||||
if (message == null || message.length <= MAC_LENGTH)
|
||||
throw new InvalidMacException("Message shorter than MAC!");
|
||||
|
||||
byte[] strippedMessage = new byte[message.length - MAC_LENGTH];
|
||||
System.arraycopy(message, 0, strippedMessage, 0, strippedMessage.length);
|
||||
return strippedMessage;
|
||||
}
|
||||
|
||||
public static void verifyMac(byte[] message, SecretKeySpec macKey) throws InvalidMacException {
|
||||
byte[] localMacBytes = calculateMac(message, 0, message.length - MAC_LENGTH, macKey);
|
||||
byte[] receivedMacBytes = new byte[MAC_LENGTH];
|
||||
|
||||
System.arraycopy(message, message.length-MAC_LENGTH, receivedMacBytes, 0, receivedMacBytes.length);
|
||||
|
||||
Log.w("mm", "Local Mac: " + Hex.toString(localMacBytes));
|
||||
Log.w("mm", "Remot Mac: " + Hex.toString(receivedMacBytes));
|
||||
|
||||
if (!Arrays.equals(localMacBytes, receivedMacBytes))
|
||||
if (!Arrays.equals(localMac, receivedMac)) {
|
||||
throw new InvalidMacException("MAC on message does not match calculated MAC.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@ import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.storage.CanonicalRecipientAddress;
|
||||
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
||||
import org.whispersystems.textsecure.storage.LocalKeyRecord;
|
||||
@@ -55,8 +56,6 @@ 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 = MessageCipher.HEADER_LENGTH + MessageMac.MAC_LENGTH;
|
||||
|
||||
public SessionCipherContext getEncryptionContext(Context context,
|
||||
MasterSecret masterSecret,
|
||||
@@ -149,20 +148,6 @@ 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) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
@@ -314,7 +299,7 @@ public class SessionCipher {
|
||||
IdentityKey remoteIdentityKey = records.getSessionRecord().getIdentityKey();
|
||||
|
||||
if (isInitiallyExchangedKeys(records, localKeyId, remoteKeyId) &&
|
||||
messageVersion >= MessageCipher.CRADLE_AGREEMENT_VERSION)
|
||||
messageVersion >= CiphertextMessage.CRADLE_AGREEMENT_VERSION)
|
||||
{
|
||||
return SharedSecretCalculator.calculateSharedSecret(localKeyPair, localIdentityKey,
|
||||
remoteKey, remoteIdentityKey);
|
||||
|
@@ -0,0 +1,144 @@
|
||||
package org.whispersystems.textsecure.crypto.protocol;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.InvalidMacException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.crypto.MessageMac;
|
||||
import org.whispersystems.textsecure.crypto.PublicKey;
|
||||
import org.whispersystems.textsecure.crypto.SessionCipher;
|
||||
import org.whispersystems.textsecure.util.Conversions;
|
||||
|
||||
public class CiphertextMessage {
|
||||
|
||||
public static final int SUPPORTED_VERSION = 2;
|
||||
public static final int CRADLE_AGREEMENT_VERSION = 2;
|
||||
|
||||
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;
|
||||
private static final int HEADER_LENGTH = VERSION_LENGTH + SENDER_KEY_ID_LENGTH +
|
||||
RECEIVER_KEY_ID_LENGTH + COUNTER_LENGTH +
|
||||
NEXT_KEY_LENGTH;
|
||||
|
||||
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 BODY_OFFSET = COUNTER_OFFSET + COUNTER_LENGTH;
|
||||
|
||||
public static final int ENCRYPTED_MESSAGE_OVERHEAD = HEADER_LENGTH + MessageMac.MAC_LENGTH;
|
||||
|
||||
private final byte[] ciphertext;
|
||||
|
||||
public CiphertextMessage(SessionCipher.SessionCipherContext sessionContext, byte[] ciphertextBody) {
|
||||
this.ciphertext = new byte[HEADER_LENGTH + ciphertextBody.length + MessageMac.MAC_LENGTH];
|
||||
setVersion(sessionContext.getMessageVersion(), SUPPORTED_VERSION);
|
||||
setSenderKeyId(sessionContext.getSenderKeyId());
|
||||
setReceiverKeyId(sessionContext.getRecipientKeyId());
|
||||
setNextKeyBytes(sessionContext.getNextKey().serialize());
|
||||
setCounter(sessionContext.getCounter());
|
||||
setBody(ciphertextBody);
|
||||
setMac(MessageMac.calculateMac(ciphertext, 0, ciphertext.length - MessageMac.MAC_LENGTH,
|
||||
sessionContext.getSessionKey().getMacKey()));
|
||||
}
|
||||
|
||||
public CiphertextMessage(byte[] ciphertext) throws InvalidMessageException {
|
||||
this.ciphertext = ciphertext;
|
||||
|
||||
if (ciphertext.length < HEADER_LENGTH) {
|
||||
throw new InvalidMessageException("Not long enough for ciphertext header!");
|
||||
}
|
||||
|
||||
if (getCurrentVersion() > SUPPORTED_VERSION) {
|
||||
throw new InvalidMessageException("Unspported version: " + getCurrentVersion());
|
||||
}
|
||||
}
|
||||
|
||||
public void setVersion(int current, int supported) {
|
||||
ciphertext[VERSION_OFFSET] = Conversions.intsToByteHighAndLow(current, supported);
|
||||
}
|
||||
|
||||
public int getCurrentVersion() {
|
||||
return Conversions.highBitsToInt(ciphertext[VERSION_OFFSET]);
|
||||
}
|
||||
|
||||
public int getSupportedVersion() {
|
||||
return Conversions.lowBitsToInt(ciphertext[VERSION_OFFSET]);
|
||||
}
|
||||
|
||||
public void setSenderKeyId(int senderKeyId) {
|
||||
Conversions.mediumToByteArray(ciphertext, SENDER_KEY_ID_OFFSET, senderKeyId);
|
||||
}
|
||||
|
||||
public int getSenderKeyId() {
|
||||
return Conversions.byteArrayToMedium(ciphertext, SENDER_KEY_ID_OFFSET);
|
||||
}
|
||||
|
||||
public void setReceiverKeyId(int receiverKeyId) {
|
||||
Conversions.mediumToByteArray(ciphertext, RECEIVER_KEY_ID_OFFSET, receiverKeyId);
|
||||
}
|
||||
|
||||
public int getReceiverKeyId() {
|
||||
return Conversions.byteArrayToMedium(ciphertext, RECEIVER_KEY_ID_OFFSET);
|
||||
}
|
||||
|
||||
public void setNextKeyBytes(byte[] nextKey) {
|
||||
assert(nextKey.length == NEXT_KEY_LENGTH);
|
||||
System.arraycopy(nextKey, 0, ciphertext, NEXT_KEY_OFFSET, nextKey.length);
|
||||
}
|
||||
|
||||
public byte[] getNextKeyBytes() {
|
||||
byte[] nextKeyBytes = new byte[NEXT_KEY_LENGTH];
|
||||
System.arraycopy(ciphertext, NEXT_KEY_OFFSET, nextKeyBytes, 0, nextKeyBytes.length);
|
||||
|
||||
return nextKeyBytes;
|
||||
}
|
||||
|
||||
public void setCounter(int counter) {
|
||||
Conversions.mediumToByteArray(ciphertext, COUNTER_OFFSET, counter);
|
||||
}
|
||||
|
||||
public int getCounter() {
|
||||
return Conversions.byteArrayToMedium(ciphertext, COUNTER_OFFSET);
|
||||
}
|
||||
|
||||
public void setBody(byte[] body) {
|
||||
System.arraycopy(body, 0, ciphertext, BODY_OFFSET, body.length);
|
||||
}
|
||||
|
||||
public byte[] getBody() {
|
||||
byte[] body = new byte[ciphertext.length - HEADER_LENGTH - MessageMac.MAC_LENGTH];
|
||||
System.arraycopy(ciphertext, BODY_OFFSET, body, 0, body.length);
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
public void setMac(byte[] mac) {
|
||||
System.arraycopy(mac, 0, ciphertext, ciphertext.length-mac.length, mac.length);
|
||||
}
|
||||
|
||||
public byte[] getMac() {
|
||||
byte[] mac = new byte[MessageMac.MAC_LENGTH];
|
||||
System.arraycopy(ciphertext, ciphertext.length-mac.length, mac, 0, mac.length);
|
||||
|
||||
return mac;
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
public void verifyMac(SessionCipher.SessionCipherContext sessionContext)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
try {
|
||||
MessageMac.verifyMac(this.ciphertext, 0, this.ciphertext.length - MessageMac.MAC_LENGTH,
|
||||
getMac(), sessionContext.getSessionKey().getMacKey());
|
||||
} catch (InvalidMacException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -18,8 +18,8 @@ package org.whispersystems.textsecure.crypto.protocol;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
||||
import org.whispersystems.textsecure.crypto.InvalidVersionException;
|
||||
import org.whispersystems.textsecure.crypto.MessageCipher;
|
||||
import org.whispersystems.textsecure.crypto.PublicKey;
|
||||
import org.whispersystems.textsecure.util.Conversions;
|
||||
|
||||
@@ -32,63 +32,52 @@ import org.whispersystems.textsecure.util.Conversions;
|
||||
*/
|
||||
public class PreKeyBundleMessage {
|
||||
|
||||
private static final int VERSION_LENGTH = MessageCipher.VERSION_LENGTH;
|
||||
public static final int SUPPORTED_VERSION = CiphertextMessage.SUPPORTED_VERSION;
|
||||
|
||||
private static final int VERSION_LENGTH = CiphertextMessage.VERSION_LENGTH;
|
||||
private static final int IDENTITY_KEY_LENGTH = IdentityKey.SIZE;
|
||||
public static final int HEADER_LENGTH = IDENTITY_KEY_LENGTH + MessageCipher.HEADER_LENGTH;
|
||||
|
||||
private static final int VERSION_OFFSET = MessageCipher.VERSION_OFFSET;
|
||||
private static final int IDENTITY_KEY_OFFSET = VERSION_OFFSET + MessageCipher.VERSION_LENGTH;
|
||||
private static final int PUBLIC_KEY_OFFSET = IDENTITY_KEY_LENGTH + MessageCipher.NEXT_KEY_OFFSET;
|
||||
private static final int PREKEY_ID_OFFSET = IDENTITY_KEY_LENGTH + MessageCipher.RECEIVER_KEY_ID_OFFSET;
|
||||
private static final int VERSION_OFFSET = CiphertextMessage.VERSION_OFFSET;
|
||||
private static final int IDENTITY_KEY_OFFSET = VERSION_OFFSET + VERSION_LENGTH;
|
||||
|
||||
private final byte[] messageBytes;
|
||||
|
||||
private final int supportedVersion;
|
||||
private final int messageVersion;
|
||||
private final int preKeyId;
|
||||
private final IdentityKey identityKey;
|
||||
private final PublicKey publicKey;
|
||||
private final byte[] bundledMessage;
|
||||
private final byte[] messageBytes;
|
||||
private final CiphertextMessage bundledMessage;
|
||||
private final IdentityKey identityKey;
|
||||
|
||||
public PreKeyBundleMessage(byte[] messageBytes)
|
||||
throws InvalidKeyException, InvalidVersionException
|
||||
throws InvalidVersionException, InvalidKeyException
|
||||
{
|
||||
this.messageBytes = messageBytes;
|
||||
this.messageVersion = Conversions.highBitsToInt(this.messageBytes[VERSION_OFFSET]);
|
||||
try {
|
||||
this.messageBytes = messageBytes;
|
||||
int messageVersion = Conversions.highBitsToInt(this.messageBytes[VERSION_OFFSET]);
|
||||
|
||||
if (messageVersion > MessageCipher.SUPPORTED_VERSION)
|
||||
throw new InvalidVersionException("Key exchange with version: " + messageVersion +
|
||||
" but we only support: " + MessageCipher.SUPPORTED_VERSION);
|
||||
if (messageVersion > CiphertextMessage.SUPPORTED_VERSION)
|
||||
throw new InvalidVersionException("Key exchange with version: " + messageVersion);
|
||||
|
||||
this.supportedVersion = Conversions.lowBitsToInt(messageBytes[VERSION_OFFSET]);
|
||||
this.publicKey = new PublicKey(messageBytes, PUBLIC_KEY_OFFSET);
|
||||
this.identityKey = new IdentityKey(messageBytes, IDENTITY_KEY_OFFSET);
|
||||
this.preKeyId = Conversions.byteArrayToMedium(messageBytes, PREKEY_ID_OFFSET);
|
||||
this.bundledMessage = new byte[messageBytes.length - IDENTITY_KEY_LENGTH];
|
||||
this.identityKey = new IdentityKey(messageBytes, IDENTITY_KEY_OFFSET);
|
||||
byte[] bundledMessageBytes = new byte[messageBytes.length - IDENTITY_KEY_LENGTH];
|
||||
|
||||
bundledMessageBytes[VERSION_OFFSET] = this.messageBytes[VERSION_OFFSET];
|
||||
System.arraycopy(messageBytes, IDENTITY_KEY_OFFSET+IDENTITY_KEY_LENGTH, bundledMessageBytes,
|
||||
VERSION_OFFSET+VERSION_LENGTH, bundledMessageBytes.length-VERSION_LENGTH);
|
||||
|
||||
this.bundledMessage[VERSION_OFFSET] = this.messageBytes[VERSION_OFFSET];
|
||||
System.arraycopy(messageBytes, IDENTITY_KEY_OFFSET+IDENTITY_KEY_LENGTH, bundledMessage, VERSION_OFFSET+VERSION_LENGTH, bundledMessage.length-VERSION_LENGTH);
|
||||
this.bundledMessage = new CiphertextMessage(bundledMessageBytes);
|
||||
} catch (InvalidMessageException e) {
|
||||
throw new InvalidKeyException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public PreKeyBundleMessage(IdentityKey identityKey, byte[] bundledMessage) {
|
||||
try {
|
||||
this.supportedVersion = MessageCipher.SUPPORTED_VERSION;
|
||||
this.messageVersion = MessageCipher.SUPPORTED_VERSION;
|
||||
this.identityKey = identityKey;
|
||||
this.publicKey = new PublicKey(bundledMessage, MessageCipher.NEXT_KEY_OFFSET);
|
||||
this.preKeyId = Conversions.byteArrayToMedium(bundledMessage, MessageCipher.RECEIVER_KEY_ID_OFFSET);
|
||||
this.bundledMessage = bundledMessage;
|
||||
this.messageBytes = new byte[IDENTITY_KEY_LENGTH + bundledMessage.length];
|
||||
public PreKeyBundleMessage(CiphertextMessage bundledMessage, IdentityKey identityKey) {
|
||||
this.bundledMessage = bundledMessage;
|
||||
this.identityKey = identityKey;
|
||||
this.messageBytes = new byte[IDENTITY_KEY_LENGTH + bundledMessage.serialize().length];
|
||||
|
||||
byte[] identityKeyBytes = identityKey.serialize();
|
||||
byte[] bundledMessageBytes = bundledMessage.serialize();
|
||||
byte[] identityKeyBytes = identityKey.serialize();
|
||||
|
||||
messageBytes[VERSION_OFFSET] = bundledMessage[VERSION_OFFSET];
|
||||
System.arraycopy(identityKeyBytes, 0, messageBytes, IDENTITY_KEY_OFFSET, identityKeyBytes.length);
|
||||
System.arraycopy(bundledMessage, VERSION_OFFSET+VERSION_LENGTH, messageBytes, IDENTITY_KEY_OFFSET+IDENTITY_KEY_LENGTH, bundledMessage.length-VERSION_LENGTH);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
messageBytes[VERSION_OFFSET] = bundledMessageBytes[VERSION_OFFSET];
|
||||
System.arraycopy(identityKeyBytes, 0, messageBytes, IDENTITY_KEY_OFFSET, identityKeyBytes.length);
|
||||
System.arraycopy(bundledMessageBytes, VERSION_OFFSET+VERSION_LENGTH, messageBytes, IDENTITY_KEY_OFFSET+IDENTITY_KEY_LENGTH, bundledMessageBytes.length-VERSION_LENGTH);
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
@@ -96,26 +85,22 @@ public class PreKeyBundleMessage {
|
||||
}
|
||||
|
||||
public int getSupportedVersion() {
|
||||
return supportedVersion;
|
||||
}
|
||||
|
||||
public int getMessageVersion() {
|
||||
return messageVersion;
|
||||
return bundledMessage.getSupportedVersion();
|
||||
}
|
||||
|
||||
public IdentityKey getIdentityKey() {
|
||||
return identityKey;
|
||||
}
|
||||
|
||||
public PublicKey getPublicKey() {
|
||||
return publicKey;
|
||||
public PublicKey getPublicKey() throws InvalidKeyException {
|
||||
return new PublicKey(bundledMessage.getNextKeyBytes());
|
||||
}
|
||||
|
||||
public byte[] getBundledMessage() {
|
||||
public CiphertextMessage getBundledMessage() {
|
||||
return bundledMessage;
|
||||
}
|
||||
|
||||
public int getPreKeyId() {
|
||||
return preKeyId;
|
||||
return bundledMessage.getReceiverKeyId();
|
||||
}
|
||||
}
|
||||
|
20
library/src/org/whispersystems/textsecure/push/PushBody.java
Normal file
20
library/src/org/whispersystems/textsecure/push/PushBody.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
public class PushBody {
|
||||
|
||||
private final int type;
|
||||
private final byte[] body;
|
||||
|
||||
public PushBody(int type, byte[] body) {
|
||||
this.type = type;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public byte[] getBody() {
|
||||
return body;
|
||||
}
|
||||
}
|
@@ -84,32 +84,31 @@ public class PushServiceSocket {
|
||||
makeRequest(REGISTER_GCM_PATH, "DELETE", null);
|
||||
}
|
||||
|
||||
public void sendMessage(PushDestination recipient, byte[] body, int type)
|
||||
public void sendMessage(PushDestination recipient, PushBody pushBody)
|
||||
throws IOException
|
||||
{
|
||||
OutgoingPushMessage message = new OutgoingPushMessage(recipient.getRelay(),
|
||||
recipient.getNumber(),
|
||||
body, type);
|
||||
pushBody.getBody(),
|
||||
pushBody.getType());
|
||||
|
||||
sendMessage(new OutgoingPushMessageList(message));
|
||||
}
|
||||
|
||||
public void sendMessage(List<PushDestination> recipients,
|
||||
List<byte[]> bodies, List<Integer> types)
|
||||
public void sendMessage(List<PushDestination> recipients, List<PushBody> bodies)
|
||||
throws IOException
|
||||
{
|
||||
List<OutgoingPushMessage> messages = new LinkedList<OutgoingPushMessage>();
|
||||
|
||||
Iterator<PushDestination> recipientsIterator = recipients.iterator();
|
||||
Iterator<byte[]> bodiesIterator = bodies.iterator();
|
||||
Iterator<Integer> typesIterator = types.iterator();
|
||||
Iterator<PushBody> bodiesIterator = bodies.iterator();
|
||||
|
||||
while (recipientsIterator.hasNext()) {
|
||||
PushDestination recipient = recipientsIterator.next();
|
||||
byte[] body = bodiesIterator.next();
|
||||
int type = typesIterator.next();
|
||||
PushBody body = bodiesIterator.next();
|
||||
|
||||
messages.add(new OutgoingPushMessage(recipient.getRelay(), recipient.getNumber(), body, type));
|
||||
messages.add(new OutgoingPushMessage(recipient.getRelay(), recipient.getNumber(),
|
||||
body.getBody(), body.getType()));
|
||||
}
|
||||
|
||||
sendMessage(new OutgoingPushMessageList(messages));
|
||||
|
Reference in New Issue
Block a user