137 lines
5.0 KiB
Java
137 lines
5.0 KiB
Java
package org.whispersystems.textsecure.push;
|
|
|
|
import android.util.Log;
|
|
|
|
import org.whispersystems.libaxolotl.InvalidVersionException;
|
|
import org.whispersystems.textsecure.util.Base64;
|
|
import org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal;
|
|
import org.whispersystems.textsecure.util.Hex;
|
|
|
|
import javax.crypto.BadPaddingException;
|
|
import javax.crypto.Cipher;
|
|
import javax.crypto.IllegalBlockSizeException;
|
|
import javax.crypto.Mac;
|
|
import javax.crypto.NoSuchPaddingException;
|
|
import javax.crypto.spec.IvParameterSpec;
|
|
import javax.crypto.spec.SecretKeySpec;
|
|
import java.io.IOException;
|
|
import java.security.InvalidAlgorithmParameterException;
|
|
import java.security.InvalidKeyException;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.util.Arrays;
|
|
|
|
public class IncomingEncryptedPushMessage {
|
|
|
|
private static final int SUPPORTED_VERSION = 1;
|
|
private static final int CIPHER_KEY_SIZE = 32;
|
|
private static final int MAC_KEY_SIZE = 20;
|
|
private static final int MAC_SIZE = 10;
|
|
|
|
private static final int VERSION_OFFSET = 0;
|
|
private static final int VERSION_LENGTH = 1;
|
|
private static final int IV_OFFSET = VERSION_OFFSET + VERSION_LENGTH;
|
|
private static final int IV_LENGTH = 16;
|
|
private static final int CIPHERTEXT_OFFSET = IV_OFFSET + IV_LENGTH;
|
|
|
|
private final IncomingPushMessage incomingPushMessage;
|
|
|
|
public IncomingEncryptedPushMessage(String message, String signalingKey)
|
|
throws IOException, InvalidVersionException
|
|
{
|
|
byte[] ciphertext = Base64.decode(message);
|
|
|
|
if (ciphertext.length < VERSION_LENGTH || ciphertext[VERSION_OFFSET] != SUPPORTED_VERSION)
|
|
throw new InvalidVersionException("Unsupported version!");
|
|
|
|
SecretKeySpec cipherKey = getCipherKey(signalingKey);
|
|
SecretKeySpec macKey = getMacKey(signalingKey);
|
|
|
|
verifyMac(ciphertext, macKey);
|
|
|
|
byte[] plaintext = getPlaintext(ciphertext, cipherKey);
|
|
IncomingPushMessageSignal signal = IncomingPushMessageSignal.parseFrom(plaintext);
|
|
|
|
this.incomingPushMessage = new IncomingPushMessage(signal);
|
|
}
|
|
|
|
public IncomingPushMessage getIncomingPushMessage() {
|
|
return incomingPushMessage;
|
|
}
|
|
|
|
private byte[] getPlaintext(byte[] ciphertext, SecretKeySpec cipherKey) throws IOException {
|
|
try {
|
|
byte[] ivBytes = new byte[IV_LENGTH];
|
|
System.arraycopy(ciphertext, IV_OFFSET, ivBytes, 0, ivBytes.length);
|
|
IvParameterSpec iv = new IvParameterSpec(ivBytes);
|
|
|
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
cipher.init(Cipher.DECRYPT_MODE, cipherKey, iv);
|
|
|
|
return cipher.doFinal(ciphertext, CIPHERTEXT_OFFSET,
|
|
ciphertext.length - VERSION_LENGTH - IV_LENGTH - MAC_SIZE);
|
|
} catch (NoSuchAlgorithmException e) {
|
|
throw new AssertionError(e);
|
|
} catch (NoSuchPaddingException e) {
|
|
throw new AssertionError(e);
|
|
} catch (InvalidKeyException e) {
|
|
throw new AssertionError(e);
|
|
} catch (InvalidAlgorithmParameterException e) {
|
|
throw new AssertionError(e);
|
|
} catch (IllegalBlockSizeException e) {
|
|
throw new AssertionError(e);
|
|
} catch (BadPaddingException e) {
|
|
Log.w("IncomingEncryptedPushMessage", e);
|
|
throw new IOException("Bad padding?");
|
|
}
|
|
}
|
|
|
|
private void verifyMac(byte[] ciphertext, SecretKeySpec macKey) throws IOException {
|
|
try {
|
|
Mac mac = Mac.getInstance("HmacSHA256");
|
|
mac.init(macKey);
|
|
|
|
if (ciphertext.length < MAC_SIZE + 1)
|
|
throw new IOException("Invalid MAC!");
|
|
|
|
mac.update(ciphertext, 0, ciphertext.length - MAC_SIZE);
|
|
|
|
byte[] ourMacFull = mac.doFinal();
|
|
byte[] ourMacBytes = new byte[MAC_SIZE];
|
|
System.arraycopy(ourMacFull, 0, ourMacBytes, 0, ourMacBytes.length);
|
|
|
|
byte[] theirMacBytes = new byte[MAC_SIZE];
|
|
System.arraycopy(ciphertext, ciphertext.length-MAC_SIZE, theirMacBytes, 0, theirMacBytes.length);
|
|
|
|
Log.w("IncomingEncryptedPushMessage", "Our MAC: " + Hex.toString(ourMacBytes));
|
|
Log.w("IncomingEncryptedPushMessage", "Thr MAC: " + Hex.toString(theirMacBytes));
|
|
|
|
if (!Arrays.equals(ourMacBytes, theirMacBytes)) {
|
|
throw new IOException("Invalid MAC compare!");
|
|
}
|
|
} catch (NoSuchAlgorithmException e) {
|
|
throw new AssertionError(e);
|
|
} catch (InvalidKeyException e) {
|
|
throw new AssertionError(e);
|
|
}
|
|
}
|
|
|
|
|
|
private SecretKeySpec getCipherKey(String signalingKey) throws IOException {
|
|
byte[] signalingKeyBytes = Base64.decode(signalingKey);
|
|
byte[] cipherKey = new byte[CIPHER_KEY_SIZE];
|
|
System.arraycopy(signalingKeyBytes, 0, cipherKey, 0, cipherKey.length);
|
|
|
|
return new SecretKeySpec(cipherKey, "AES");
|
|
}
|
|
|
|
|
|
private SecretKeySpec getMacKey(String signalingKey) throws IOException {
|
|
byte[] signalingKeyBytes = Base64.decode(signalingKey);
|
|
byte[] macKey = new byte[MAC_KEY_SIZE];
|
|
System.arraycopy(signalingKeyBytes, CIPHER_KEY_SIZE, macKey, 0, macKey.length);
|
|
|
|
return new SecretKeySpec(macKey, "HmacSHA256");
|
|
}
|
|
|
|
}
|