mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-25 01:07:47 +00:00
Collapse some v2 interfaces now that there's no v1.
This commit is contained in:
parent
1d07ca3e6f
commit
cebad39422
@ -18,29 +18,258 @@ package org.whispersystems.textsecure.crypto;
|
|||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||||
|
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
|
||||||
|
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||||
|
import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage;
|
||||||
|
import org.whispersystems.textsecure.crypto.protocol.WhisperMessage;
|
||||||
|
import org.whispersystems.textsecure.crypto.ratchet.ChainKey;
|
||||||
|
import org.whispersystems.textsecure.crypto.ratchet.MessageKeys;
|
||||||
|
import org.whispersystems.textsecure.crypto.ratchet.RootKey;
|
||||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||||
import org.whispersystems.textsecure.storage.SessionRecordV1;
|
|
||||||
import org.whispersystems.textsecure.storage.SessionRecordV2;
|
import org.whispersystems.textsecure.storage.SessionRecordV2;
|
||||||
|
import org.whispersystems.textsecure.storage.SessionState;
|
||||||
|
import org.whispersystems.textsecure.util.Conversions;
|
||||||
|
|
||||||
public abstract class SessionCipher {
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
protected static final Object SESSION_LOCK = new Object();
|
import javax.crypto.BadPaddingException;
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
public abstract CiphertextMessage encrypt(byte[] paddedMessage);
|
public class SessionCipher {
|
||||||
public abstract byte[] decrypt(byte[] decodedMessage) throws InvalidMessageException, DuplicateMessageException, LegacyMessageException;
|
|
||||||
public abstract int getRemoteRegistrationId();
|
private static final Object SESSION_LOCK = new Object();
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final MasterSecret masterSecret;
|
||||||
|
private final RecipientDevice recipient;
|
||||||
|
|
||||||
public static SessionCipher createFor(Context context,
|
public static SessionCipher createFor(Context context,
|
||||||
MasterSecret masterSecret,
|
MasterSecret masterSecret,
|
||||||
RecipientDevice recipient)
|
RecipientDevice recipient)
|
||||||
{
|
{
|
||||||
if (SessionRecordV2.hasSession(context, masterSecret, recipient)) {
|
if (SessionRecordV2.hasSession(context, masterSecret, recipient)) {
|
||||||
return new SessionCipherV2(context, masterSecret, recipient);
|
return new SessionCipher(context, masterSecret, recipient);
|
||||||
} else {
|
} else {
|
||||||
throw new AssertionError("Attempt to initialize cipher for non-existing session.");
|
throw new AssertionError("Attempt to initialize cipher for non-existing session.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private SessionCipher(Context context, MasterSecret masterSecret, RecipientDevice recipient) {
|
||||||
|
this.recipient = recipient;
|
||||||
|
this.masterSecret = masterSecret;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CiphertextMessage encrypt(byte[] paddedMessage) {
|
||||||
|
synchronized (SESSION_LOCK) {
|
||||||
|
SessionRecordV2 sessionRecord = getSessionRecord();
|
||||||
|
SessionState sessionState = sessionRecord.getSessionState();
|
||||||
|
ChainKey chainKey = sessionState.getSenderChainKey();
|
||||||
|
MessageKeys messageKeys = chainKey.getMessageKeys();
|
||||||
|
ECPublicKey senderEphemeral = sessionState.getSenderEphemeral();
|
||||||
|
int previousCounter = sessionState.getPreviousCounter();
|
||||||
|
|
||||||
|
byte[] ciphertextBody = getCiphertext(messageKeys, paddedMessage);
|
||||||
|
CiphertextMessage ciphertextMessage = new WhisperMessage(messageKeys.getMacKey(),
|
||||||
|
senderEphemeral, chainKey.getIndex(),
|
||||||
|
previousCounter, ciphertextBody);
|
||||||
|
|
||||||
|
if (sessionState.hasPendingPreKey()) {
|
||||||
|
Pair<Integer, ECPublicKey> pendingPreKey = sessionState.getPendingPreKey();
|
||||||
|
int localRegistrationId = sessionState.getLocalRegistrationId();
|
||||||
|
|
||||||
|
ciphertextMessage = new PreKeyWhisperMessage(localRegistrationId, pendingPreKey.first,
|
||||||
|
pendingPreKey.second,
|
||||||
|
sessionState.getLocalIdentityKey(),
|
||||||
|
(WhisperMessage) ciphertextMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionState.setSenderChainKey(chainKey.getNextChainKey());
|
||||||
|
sessionRecord.save();
|
||||||
|
|
||||||
|
return ciphertextMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] decrypt(byte[] decodedMessage)
|
||||||
|
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException
|
||||||
|
{
|
||||||
|
synchronized (SESSION_LOCK) {
|
||||||
|
SessionRecordV2 sessionRecord = getSessionRecord();
|
||||||
|
SessionState sessionState = sessionRecord.getSessionState();
|
||||||
|
List<SessionState> previousStates = sessionRecord.getPreviousSessions();
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] plaintext = decrypt(sessionState, decodedMessage);
|
||||||
|
sessionRecord.save();
|
||||||
|
|
||||||
|
return plaintext;
|
||||||
|
} catch (InvalidMessageException e) {
|
||||||
|
Log.w("SessionCipherV2", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (SessionState previousState : previousStates) {
|
||||||
|
try {
|
||||||
|
Log.w("SessionCipherV2", "Attempting decrypt on previous state...");
|
||||||
|
byte[] plaintext = decrypt(previousState, decodedMessage);
|
||||||
|
sessionRecord.save();
|
||||||
|
|
||||||
|
return plaintext;
|
||||||
|
} catch (InvalidMessageException e) {
|
||||||
|
Log.w("SessionCipherV2", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidMessageException("No valid sessions.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] decrypt(SessionState sessionState, byte[] decodedMessage)
|
||||||
|
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException
|
||||||
|
{
|
||||||
|
if (!sessionState.hasSenderChain()) {
|
||||||
|
throw new InvalidMessageException("Uninitialized session!");
|
||||||
|
}
|
||||||
|
|
||||||
|
WhisperMessage ciphertextMessage = new WhisperMessage(decodedMessage);
|
||||||
|
ECPublicKey theirEphemeral = ciphertextMessage.getSenderEphemeral();
|
||||||
|
int counter = ciphertextMessage.getCounter();
|
||||||
|
ChainKey chainKey = getOrCreateChainKey(sessionState, theirEphemeral);
|
||||||
|
MessageKeys messageKeys = getOrCreateMessageKeys(sessionState, theirEphemeral,
|
||||||
|
chainKey, counter);
|
||||||
|
|
||||||
|
ciphertextMessage.verifyMac(messageKeys.getMacKey());
|
||||||
|
|
||||||
|
byte[] plaintext = getPlaintext(messageKeys, ciphertextMessage.getBody());
|
||||||
|
|
||||||
|
sessionState.clearPendingPreKey();
|
||||||
|
|
||||||
|
return plaintext;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRemoteRegistrationId() {
|
||||||
|
synchronized (SESSION_LOCK) {
|
||||||
|
SessionRecordV2 sessionRecord = getSessionRecord();
|
||||||
|
return sessionRecord.getSessionState().getRemoteRegistrationId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChainKey getOrCreateChainKey(SessionState sessionState, ECPublicKey theirEphemeral)
|
||||||
|
throws InvalidMessageException
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (sessionState.hasReceiverChain(theirEphemeral)) {
|
||||||
|
return sessionState.getReceiverChainKey(theirEphemeral);
|
||||||
|
} else {
|
||||||
|
RootKey rootKey = sessionState.getRootKey();
|
||||||
|
ECKeyPair ourEphemeral = sessionState.getSenderEphemeralPair();
|
||||||
|
Pair<RootKey, ChainKey> receiverChain = rootKey.createChain(theirEphemeral, ourEphemeral);
|
||||||
|
ECKeyPair ourNewEphemeral = Curve.generateKeyPair(true);
|
||||||
|
Pair<RootKey, ChainKey> senderChain = receiverChain.first.createChain(theirEphemeral, ourNewEphemeral);
|
||||||
|
|
||||||
|
sessionState.setRootKey(senderChain.first);
|
||||||
|
sessionState.addReceiverChain(theirEphemeral, receiverChain.second);
|
||||||
|
sessionState.setPreviousCounter(sessionState.getSenderChainKey().getIndex()-1);
|
||||||
|
sessionState.setSenderChain(ourNewEphemeral, senderChain.second);
|
||||||
|
|
||||||
|
return receiverChain.second;
|
||||||
|
}
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
throw new InvalidMessageException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MessageKeys getOrCreateMessageKeys(SessionState sessionState,
|
||||||
|
ECPublicKey theirEphemeral,
|
||||||
|
ChainKey chainKey, int counter)
|
||||||
|
throws InvalidMessageException, DuplicateMessageException
|
||||||
|
{
|
||||||
|
if (chainKey.getIndex() > counter) {
|
||||||
|
if (sessionState.hasMessageKeys(theirEphemeral, counter)) {
|
||||||
|
return sessionState.removeMessageKeys(theirEphemeral, counter);
|
||||||
|
} else {
|
||||||
|
throw new DuplicateMessageException("Received message with old counter: " +
|
||||||
|
chainKey.getIndex() + " , " + counter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chainKey.getIndex() - counter > 2000) {
|
||||||
|
throw new InvalidMessageException("Over 2000 messages into the future!");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (chainKey.getIndex() < counter) {
|
||||||
|
MessageKeys messageKeys = chainKey.getMessageKeys();
|
||||||
|
sessionState.setMessageKeys(theirEphemeral, messageKeys);
|
||||||
|
chainKey = chainKey.getNextChainKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionState.setReceiverChainKey(theirEphemeral, chainKey.getNextChainKey());
|
||||||
|
return chainKey.getMessageKeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] getCiphertext(MessageKeys messageKeys, byte[] plaintext) {
|
||||||
|
try {
|
||||||
|
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE,
|
||||||
|
messageKeys.getCipherKey(),
|
||||||
|
messageKeys.getCounter());
|
||||||
|
|
||||||
|
return cipher.doFinal(plaintext);
|
||||||
|
} catch (IllegalBlockSizeException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
} catch (BadPaddingException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] getPlaintext(MessageKeys messageKeys, byte[] cipherText) {
|
||||||
|
try {
|
||||||
|
Cipher cipher = getCipher(Cipher.DECRYPT_MODE,
|
||||||
|
messageKeys.getCipherKey(),
|
||||||
|
messageKeys.getCounter());
|
||||||
|
return cipher.doFinal(cipherText);
|
||||||
|
} catch (IllegalBlockSizeException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
} catch (BadPaddingException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cipher getCipher(int mode, SecretKeySpec key, int counter) {
|
||||||
|
try {
|
||||||
|
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||||
|
|
||||||
|
byte[] ivBytes = new byte[16];
|
||||||
|
Conversions.intToByteArray(ivBytes, 0, counter);
|
||||||
|
|
||||||
|
IvParameterSpec iv = new IvParameterSpec(ivBytes);
|
||||||
|
cipher.init(mode, key, iv);
|
||||||
|
|
||||||
|
return cipher;
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
} catch (NoSuchPaddingException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
} catch (java.security.InvalidKeyException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SessionRecordV2 getSessionRecord() {
|
||||||
|
return new SessionRecordV2(context, masterSecret, recipient);
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,252 +0,0 @@
|
|||||||
package org.whispersystems.textsecure.crypto;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.util.Pair;
|
|
||||||
|
|
||||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
|
||||||
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
|
|
||||||
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
|
||||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
|
||||||
import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage;
|
|
||||||
import org.whispersystems.textsecure.crypto.protocol.WhisperMessageV2;
|
|
||||||
import org.whispersystems.textsecure.crypto.ratchet.ChainKey;
|
|
||||||
import org.whispersystems.textsecure.crypto.ratchet.MessageKeys;
|
|
||||||
import org.whispersystems.textsecure.crypto.ratchet.RootKey;
|
|
||||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
|
||||||
import org.whispersystems.textsecure.storage.SessionRecordV2;
|
|
||||||
import org.whispersystems.textsecure.storage.SessionState;
|
|
||||||
import org.whispersystems.textsecure.util.Conversions;
|
|
||||||
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
|
||||||
import javax.crypto.NoSuchPaddingException;
|
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
|
|
||||||
public class SessionCipherV2 extends SessionCipher {
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final MasterSecret masterSecret;
|
|
||||||
private final RecipientDevice recipient;
|
|
||||||
|
|
||||||
public SessionCipherV2(Context context,
|
|
||||||
MasterSecret masterSecret,
|
|
||||||
RecipientDevice recipient)
|
|
||||||
{
|
|
||||||
this.context = context;
|
|
||||||
this.masterSecret = masterSecret;
|
|
||||||
this.recipient = recipient;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CiphertextMessage encrypt(byte[] paddedMessage) {
|
|
||||||
synchronized (SESSION_LOCK) {
|
|
||||||
SessionRecordV2 sessionRecord = getSessionRecord();
|
|
||||||
SessionState sessionState = sessionRecord.getSessionState();
|
|
||||||
ChainKey chainKey = sessionState.getSenderChainKey();
|
|
||||||
MessageKeys messageKeys = chainKey.getMessageKeys();
|
|
||||||
ECPublicKey senderEphemeral = sessionState.getSenderEphemeral();
|
|
||||||
int previousCounter = sessionState.getPreviousCounter();
|
|
||||||
|
|
||||||
byte[] ciphertextBody = getCiphertext(messageKeys, paddedMessage);
|
|
||||||
CiphertextMessage ciphertextMessage = new WhisperMessageV2(messageKeys.getMacKey(),
|
|
||||||
senderEphemeral, chainKey.getIndex(),
|
|
||||||
previousCounter, ciphertextBody);
|
|
||||||
|
|
||||||
if (sessionState.hasPendingPreKey()) {
|
|
||||||
Pair<Integer, ECPublicKey> pendingPreKey = sessionState.getPendingPreKey();
|
|
||||||
int localRegistrationId = sessionState.getLocalRegistrationId();
|
|
||||||
|
|
||||||
ciphertextMessage = new PreKeyWhisperMessage(localRegistrationId, pendingPreKey.first,
|
|
||||||
pendingPreKey.second,
|
|
||||||
sessionState.getLocalIdentityKey(),
|
|
||||||
(WhisperMessageV2) ciphertextMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionState.setSenderChainKey(chainKey.getNextChainKey());
|
|
||||||
sessionRecord.save();
|
|
||||||
|
|
||||||
return ciphertextMessage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] decrypt(byte[] decodedMessage)
|
|
||||||
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException
|
|
||||||
{
|
|
||||||
synchronized (SESSION_LOCK) {
|
|
||||||
SessionRecordV2 sessionRecord = getSessionRecord();
|
|
||||||
SessionState sessionState = sessionRecord.getSessionState();
|
|
||||||
List<SessionState> previousStates = sessionRecord.getPreviousSessions();
|
|
||||||
|
|
||||||
try {
|
|
||||||
byte[] plaintext = decrypt(sessionState, decodedMessage);
|
|
||||||
sessionRecord.save();
|
|
||||||
|
|
||||||
return plaintext;
|
|
||||||
} catch (InvalidMessageException e) {
|
|
||||||
Log.w("SessionCipherV2", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (SessionState previousState : previousStates) {
|
|
||||||
try {
|
|
||||||
Log.w("SessionCipherV2", "Attempting decrypt on previous state...");
|
|
||||||
byte[] plaintext = decrypt(previousState, decodedMessage);
|
|
||||||
sessionRecord.save();
|
|
||||||
|
|
||||||
return plaintext;
|
|
||||||
} catch (InvalidMessageException e) {
|
|
||||||
Log.w("SessionCipherV2", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new InvalidMessageException("No valid sessions.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] decrypt(SessionState sessionState, byte[] decodedMessage)
|
|
||||||
throws InvalidMessageException, DuplicateMessageException, LegacyMessageException
|
|
||||||
{
|
|
||||||
if (!sessionState.hasSenderChain()) {
|
|
||||||
throw new InvalidMessageException("Uninitialized session!");
|
|
||||||
}
|
|
||||||
|
|
||||||
WhisperMessageV2 ciphertextMessage = new WhisperMessageV2(decodedMessage);
|
|
||||||
ECPublicKey theirEphemeral = ciphertextMessage.getSenderEphemeral();
|
|
||||||
int counter = ciphertextMessage.getCounter();
|
|
||||||
ChainKey chainKey = getOrCreateChainKey(sessionState, theirEphemeral);
|
|
||||||
MessageKeys messageKeys = getOrCreateMessageKeys(sessionState, theirEphemeral,
|
|
||||||
chainKey, counter);
|
|
||||||
|
|
||||||
ciphertextMessage.verifyMac(messageKeys.getMacKey());
|
|
||||||
|
|
||||||
byte[] plaintext = getPlaintext(messageKeys, ciphertextMessage.getBody());
|
|
||||||
|
|
||||||
sessionState.clearPendingPreKey();
|
|
||||||
|
|
||||||
return plaintext;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getRemoteRegistrationId() {
|
|
||||||
synchronized (SESSION_LOCK) {
|
|
||||||
SessionRecordV2 sessionRecord = getSessionRecord();
|
|
||||||
return sessionRecord.getSessionState().getRemoteRegistrationId();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ChainKey getOrCreateChainKey(SessionState sessionState, ECPublicKey theirEphemeral)
|
|
||||||
throws InvalidMessageException
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
if (sessionState.hasReceiverChain(theirEphemeral)) {
|
|
||||||
return sessionState.getReceiverChainKey(theirEphemeral);
|
|
||||||
} else {
|
|
||||||
RootKey rootKey = sessionState.getRootKey();
|
|
||||||
ECKeyPair ourEphemeral = sessionState.getSenderEphemeralPair();
|
|
||||||
Pair<RootKey, ChainKey> receiverChain = rootKey.createChain(theirEphemeral, ourEphemeral);
|
|
||||||
ECKeyPair ourNewEphemeral = Curve.generateKeyPair(true);
|
|
||||||
Pair<RootKey, ChainKey> senderChain = receiverChain.first.createChain(theirEphemeral, ourNewEphemeral);
|
|
||||||
|
|
||||||
sessionState.setRootKey(senderChain.first);
|
|
||||||
sessionState.addReceiverChain(theirEphemeral, receiverChain.second);
|
|
||||||
sessionState.setPreviousCounter(sessionState.getSenderChainKey().getIndex()-1);
|
|
||||||
sessionState.setSenderChain(ourNewEphemeral, senderChain.second);
|
|
||||||
|
|
||||||
return receiverChain.second;
|
|
||||||
}
|
|
||||||
} catch (InvalidKeyException e) {
|
|
||||||
throw new InvalidMessageException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private MessageKeys getOrCreateMessageKeys(SessionState sessionState,
|
|
||||||
ECPublicKey theirEphemeral,
|
|
||||||
ChainKey chainKey, int counter)
|
|
||||||
throws InvalidMessageException, DuplicateMessageException
|
|
||||||
{
|
|
||||||
if (chainKey.getIndex() > counter) {
|
|
||||||
if (sessionState.hasMessageKeys(theirEphemeral, counter)) {
|
|
||||||
return sessionState.removeMessageKeys(theirEphemeral, counter);
|
|
||||||
} else {
|
|
||||||
throw new DuplicateMessageException("Received message with old counter: " +
|
|
||||||
chainKey.getIndex() + " , " + counter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chainKey.getIndex() - counter > 2000) {
|
|
||||||
throw new InvalidMessageException("Over 2000 messages into the future!");
|
|
||||||
}
|
|
||||||
|
|
||||||
while (chainKey.getIndex() < counter) {
|
|
||||||
MessageKeys messageKeys = chainKey.getMessageKeys();
|
|
||||||
sessionState.setMessageKeys(theirEphemeral, messageKeys);
|
|
||||||
chainKey = chainKey.getNextChainKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionState.setReceiverChainKey(theirEphemeral, chainKey.getNextChainKey());
|
|
||||||
return chainKey.getMessageKeys();
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] getCiphertext(MessageKeys messageKeys, byte[] plaintext) {
|
|
||||||
try {
|
|
||||||
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE,
|
|
||||||
messageKeys.getCipherKey(),
|
|
||||||
messageKeys.getCounter());
|
|
||||||
|
|
||||||
return cipher.doFinal(plaintext);
|
|
||||||
} catch (IllegalBlockSizeException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
} catch (BadPaddingException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] getPlaintext(MessageKeys messageKeys, byte[] cipherText) {
|
|
||||||
try {
|
|
||||||
Cipher cipher = getCipher(Cipher.DECRYPT_MODE,
|
|
||||||
messageKeys.getCipherKey(),
|
|
||||||
messageKeys.getCounter());
|
|
||||||
return cipher.doFinal(cipherText);
|
|
||||||
} catch (IllegalBlockSizeException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
} catch (BadPaddingException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Cipher getCipher(int mode, SecretKeySpec key, int counter) {
|
|
||||||
try {
|
|
||||||
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
|
||||||
|
|
||||||
byte[] ivBytes = new byte[16];
|
|
||||||
Conversions.intToByteArray(ivBytes, 0, counter);
|
|
||||||
|
|
||||||
IvParameterSpec iv = new IvParameterSpec(ivBytes);
|
|
||||||
cipher.init(mode, key, iv);
|
|
||||||
|
|
||||||
return cipher;
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
} catch (NoSuchPaddingException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
} catch (java.security.InvalidKeyException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
} catch (InvalidAlgorithmParameterException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private SessionRecordV2 getSessionRecord() {
|
|
||||||
return new SessionRecordV2(context, masterSecret, recipient);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -20,7 +20,7 @@ public class PreKeyWhisperMessage implements CiphertextMessage {
|
|||||||
private final int preKeyId;
|
private final int preKeyId;
|
||||||
private final ECPublicKey baseKey;
|
private final ECPublicKey baseKey;
|
||||||
private final IdentityKey identityKey;
|
private final IdentityKey identityKey;
|
||||||
private final WhisperMessageV2 message;
|
private final WhisperMessage message;
|
||||||
private final byte[] serialized;
|
private final byte[] serialized;
|
||||||
|
|
||||||
public PreKeyWhisperMessage(byte[] serialized)
|
public PreKeyWhisperMessage(byte[] serialized)
|
||||||
@ -50,7 +50,7 @@ public class PreKeyWhisperMessage implements CiphertextMessage {
|
|||||||
this.preKeyId = preKeyWhisperMessage.getPreKeyId();
|
this.preKeyId = preKeyWhisperMessage.getPreKeyId();
|
||||||
this.baseKey = Curve.decodePoint(preKeyWhisperMessage.getBaseKey().toByteArray(), 0);
|
this.baseKey = Curve.decodePoint(preKeyWhisperMessage.getBaseKey().toByteArray(), 0);
|
||||||
this.identityKey = new IdentityKey(Curve.decodePoint(preKeyWhisperMessage.getIdentityKey().toByteArray(), 0));
|
this.identityKey = new IdentityKey(Curve.decodePoint(preKeyWhisperMessage.getIdentityKey().toByteArray(), 0));
|
||||||
this.message = new WhisperMessageV2(preKeyWhisperMessage.getMessage().toByteArray());
|
this.message = new WhisperMessage(preKeyWhisperMessage.getMessage().toByteArray());
|
||||||
} catch (InvalidProtocolBufferException e) {
|
} catch (InvalidProtocolBufferException e) {
|
||||||
throw new InvalidMessageException(e);
|
throw new InvalidMessageException(e);
|
||||||
} catch (InvalidKeyException e) {
|
} catch (InvalidKeyException e) {
|
||||||
@ -61,7 +61,7 @@ public class PreKeyWhisperMessage implements CiphertextMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public PreKeyWhisperMessage(int registrationId, int preKeyId, ECPublicKey baseKey,
|
public PreKeyWhisperMessage(int registrationId, int preKeyId, ECPublicKey baseKey,
|
||||||
IdentityKey identityKey, WhisperMessageV2 message)
|
IdentityKey identityKey, WhisperMessage message)
|
||||||
{
|
{
|
||||||
this.version = CiphertextMessage.CURRENT_VERSION;
|
this.version = CiphertextMessage.CURRENT_VERSION;
|
||||||
this.registrationId = registrationId;
|
this.registrationId = registrationId;
|
||||||
@ -98,7 +98,7 @@ public class PreKeyWhisperMessage implements CiphertextMessage {
|
|||||||
return baseKey;
|
return baseKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WhisperMessageV2 getWhisperMessage() {
|
public WhisperMessage getWhisperMessage() {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package org.whispersystems.textsecure.crypto.protocol;
|
package org.whispersystems.textsecure.crypto.protocol;
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
|
|
||||||
@ -10,19 +8,17 @@ import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
|||||||
import org.whispersystems.textsecure.crypto.LegacyMessageException;
|
import org.whispersystems.textsecure.crypto.LegacyMessageException;
|
||||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||||
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
|
||||||
import org.whispersystems.textsecure.crypto.protocol.WhisperProtos.WhisperMessage;
|
|
||||||
import org.whispersystems.textsecure.util.Conversions;
|
import org.whispersystems.textsecure.util.Conversions;
|
||||||
import org.whispersystems.textsecure.util.Hex;
|
|
||||||
import org.whispersystems.textsecure.util.Util;
|
import org.whispersystems.textsecure.util.Util;
|
||||||
|
|
||||||
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import javax.crypto.Mac;
|
import javax.crypto.Mac;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
public class WhisperMessageV2 implements CiphertextMessage {
|
public class WhisperMessage implements CiphertextMessage {
|
||||||
|
|
||||||
private static final int MAC_LENGTH = 8;
|
private static final int MAC_LENGTH = 8;
|
||||||
|
|
||||||
@ -32,7 +28,7 @@ public class WhisperMessageV2 implements CiphertextMessage {
|
|||||||
private final byte[] ciphertext;
|
private final byte[] ciphertext;
|
||||||
private final byte[] serialized;
|
private final byte[] serialized;
|
||||||
|
|
||||||
public WhisperMessageV2(byte[] serialized) throws InvalidMessageException, LegacyMessageException {
|
public WhisperMessage(byte[] serialized) throws InvalidMessageException, LegacyMessageException {
|
||||||
try {
|
try {
|
||||||
byte[][] messageParts = Util.split(serialized, 1, serialized.length - 1 - MAC_LENGTH, MAC_LENGTH);
|
byte[][] messageParts = Util.split(serialized, 1, serialized.length - 1 - MAC_LENGTH, MAC_LENGTH);
|
||||||
byte version = messageParts[0][0];
|
byte version = messageParts[0][0];
|
||||||
@ -47,7 +43,7 @@ public class WhisperMessageV2 implements CiphertextMessage {
|
|||||||
throw new InvalidMessageException("Unknown version: " + Conversions.highBitsToInt(version));
|
throw new InvalidMessageException("Unknown version: " + Conversions.highBitsToInt(version));
|
||||||
}
|
}
|
||||||
|
|
||||||
WhisperMessage whisperMessage = WhisperMessage.parseFrom(message);
|
WhisperProtos.WhisperMessage whisperMessage = WhisperProtos.WhisperMessage.parseFrom(message);
|
||||||
|
|
||||||
if (!whisperMessage.hasCiphertext() ||
|
if (!whisperMessage.hasCiphertext() ||
|
||||||
!whisperMessage.hasCounter() ||
|
!whisperMessage.hasCounter() ||
|
||||||
@ -70,11 +66,11 @@ public class WhisperMessageV2 implements CiphertextMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public WhisperMessageV2(SecretKeySpec macKey, ECPublicKey senderEphemeral,
|
public WhisperMessage(SecretKeySpec macKey, ECPublicKey senderEphemeral,
|
||||||
int counter, int previousCounter, byte[] ciphertext)
|
int counter, int previousCounter, byte[] ciphertext)
|
||||||
{
|
{
|
||||||
byte[] version = {Conversions.intsToByteHighAndLow(CURRENT_VERSION, CURRENT_VERSION)};
|
byte[] version = {Conversions.intsToByteHighAndLow(CURRENT_VERSION, CURRENT_VERSION)};
|
||||||
byte[] message = WhisperMessage.newBuilder()
|
byte[] message = WhisperProtos.WhisperMessage.newBuilder()
|
||||||
.setEphemeralKey(ByteString.copyFrom(senderEphemeral.serialize()))
|
.setEphemeralKey(ByteString.copyFrom(senderEphemeral.serialize()))
|
||||||
.setCounter(counter)
|
.setCounter(counter)
|
||||||
.setPreviousCounter(previousCounter)
|
.setPreviousCounter(previousCounter)
|
||||||
@ -108,7 +104,7 @@ public class WhisperMessageV2 implements CiphertextMessage {
|
|||||||
byte[] ourMac = getMac(macKey, parts[0]);
|
byte[] ourMac = getMac(macKey, parts[0]);
|
||||||
byte[] theirMac = parts[1];
|
byte[] theirMac = parts[1];
|
||||||
|
|
||||||
if (!Arrays.equals(ourMac, theirMac)) {
|
if (!MessageDigest.isEqual(ourMac, theirMac)) {
|
||||||
throw new InvalidMessageException("Bad Mac!");
|
throw new InvalidMessageException("Bad Mac!");
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -47,7 +47,7 @@ import org.whispersystems.textsecure.crypto.InvalidMessageException;
|
|||||||
import org.whispersystems.textsecure.crypto.InvalidVersionException;
|
import org.whispersystems.textsecure.crypto.InvalidVersionException;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage;
|
import org.whispersystems.textsecure.crypto.protocol.PreKeyWhisperMessage;
|
||||||
import org.whispersystems.textsecure.crypto.protocol.WhisperMessageV2;
|
import org.whispersystems.textsecure.crypto.protocol.WhisperMessage;
|
||||||
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
||||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ public class SmsReceiver {
|
|||||||
if (processor.isTrusted(preKeyExchange)) {
|
if (processor.isTrusted(preKeyExchange)) {
|
||||||
processor.processKeyExchangeMessage(preKeyExchange);
|
processor.processKeyExchangeMessage(preKeyExchange);
|
||||||
|
|
||||||
WhisperMessageV2 ciphertextMessage = preKeyExchange.getWhisperMessage();
|
WhisperMessage ciphertextMessage = preKeyExchange.getWhisperMessage();
|
||||||
String bundledMessageBody = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize()));
|
String bundledMessageBody = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize()));
|
||||||
IncomingEncryptedMessage bundledMessage = new IncomingEncryptedMessage(message, bundledMessageBody);
|
IncomingEncryptedMessage bundledMessage = new IncomingEncryptedMessage(message, bundledMessageBody);
|
||||||
Pair<Long, Long> messageAndThreadId = storeSecureMessage(masterSecret, bundledMessage);
|
Pair<Long, Long> messageAndThreadId = storeSecureMessage(masterSecret, bundledMessage);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user