mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-23 20:48:43 +00:00
1) Add encryption support for the transport layer. This obscures metadata from the push messaging provider. 2) Better support the direction multiple destination messages is headed (one unique message per recipient).
137 lines
5.0 KiB
Java
137 lines
5.0 KiB
Java
package org.whispersystems.textsecure.push;
|
|
|
|
import android.util.Log;
|
|
|
|
import org.whispersystems.textsecure.crypto.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");
|
|
}
|
|
|
|
}
|