aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/org/whispersystems/libaxolotl/protocol/WhisperMessage.java
diff options
context:
space:
mode:
authorMoxie Marlinspike <moxie@thoughtcrime.org>2014-11-24 12:54:30 -0800
committerMoxie Marlinspike <moxie@thoughtcrime.org>2014-11-24 12:54:30 -0800
commit60800e155612bea797eed93c67046a23d26054cc (patch)
treed88368c1c26162e27e790195133ca2b526597afe /src/main/java/org/whispersystems/libaxolotl/protocol/WhisperMessage.java
Break out into separate repo.
Diffstat (limited to 'src/main/java/org/whispersystems/libaxolotl/protocol/WhisperMessage.java')
-rw-r--r--src/main/java/org/whispersystems/libaxolotl/protocol/WhisperMessage.java172
1 files changed, 172 insertions, 0 deletions
diff --git a/src/main/java/org/whispersystems/libaxolotl/protocol/WhisperMessage.java b/src/main/java/org/whispersystems/libaxolotl/protocol/WhisperMessage.java
new file mode 100644
index 00000000..980bec1f
--- /dev/null
+++ b/src/main/java/org/whispersystems/libaxolotl/protocol/WhisperMessage.java
@@ -0,0 +1,172 @@
+/**
+ * Copyright (C) 2014 Open 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.libaxolotl.protocol;
+
+import com.google.protobuf.ByteString;
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import org.whispersystems.libaxolotl.IdentityKey;
+import org.whispersystems.libaxolotl.InvalidKeyException;
+import org.whispersystems.libaxolotl.InvalidMessageException;
+import org.whispersystems.libaxolotl.LegacyMessageException;
+import org.whispersystems.libaxolotl.ecc.Curve;
+import org.whispersystems.libaxolotl.ecc.ECPublicKey;
+import org.whispersystems.libaxolotl.util.ByteUtil;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.text.ParseException;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+public class WhisperMessage implements CiphertextMessage {
+
+ private static final int MAC_LENGTH = 8;
+
+ private final int messageVersion;
+ private final ECPublicKey senderRatchetKey;
+ private final int counter;
+ private final int previousCounter;
+ private final byte[] ciphertext;
+ private final byte[] serialized;
+
+ public WhisperMessage(byte[] serialized) throws InvalidMessageException, LegacyMessageException {
+ try {
+ byte[][] messageParts = ByteUtil.split(serialized, 1, serialized.length - 1 - MAC_LENGTH, MAC_LENGTH);
+ byte version = messageParts[0][0];
+ byte[] message = messageParts[1];
+ byte[] mac = messageParts[2];
+
+ if (ByteUtil.highBitsToInt(version) <= CiphertextMessage.UNSUPPORTED_VERSION) {
+ throw new LegacyMessageException("Legacy message: " + ByteUtil.highBitsToInt(version));
+ }
+
+ if (ByteUtil.highBitsToInt(version) > CURRENT_VERSION) {
+ throw new InvalidMessageException("Unknown version: " + ByteUtil.highBitsToInt(version));
+ }
+
+ WhisperProtos.WhisperMessage whisperMessage = WhisperProtos.WhisperMessage.parseFrom(message);
+
+ if (!whisperMessage.hasCiphertext() ||
+ !whisperMessage.hasCounter() ||
+ !whisperMessage.hasRatchetKey())
+ {
+ throw new InvalidMessageException("Incomplete message.");
+ }
+
+ this.serialized = serialized;
+ this.senderRatchetKey = Curve.decodePoint(whisperMessage.getRatchetKey().toByteArray(), 0);
+ this.messageVersion = ByteUtil.highBitsToInt(version);
+ this.counter = whisperMessage.getCounter();
+ this.previousCounter = whisperMessage.getPreviousCounter();
+ this.ciphertext = whisperMessage.getCiphertext().toByteArray();
+ } catch (InvalidProtocolBufferException | InvalidKeyException | ParseException e) {
+ throw new InvalidMessageException(e);
+ }
+ }
+
+ public WhisperMessage(int messageVersion, SecretKeySpec macKey, ECPublicKey senderRatchetKey,
+ int counter, int previousCounter, byte[] ciphertext,
+ IdentityKey senderIdentityKey,
+ IdentityKey receiverIdentityKey)
+ {
+ byte[] version = {ByteUtil.intsToByteHighAndLow(messageVersion, CURRENT_VERSION)};
+ byte[] message = WhisperProtos.WhisperMessage.newBuilder()
+ .setRatchetKey(ByteString.copyFrom(senderRatchetKey.serialize()))
+ .setCounter(counter)
+ .setPreviousCounter(previousCounter)
+ .setCiphertext(ByteString.copyFrom(ciphertext))
+ .build().toByteArray();
+
+ byte[] mac = getMac(messageVersion, senderIdentityKey, receiverIdentityKey, macKey,
+ ByteUtil.combine(version, message));
+
+ this.serialized = ByteUtil.combine(version, message, mac);
+ this.senderRatchetKey = senderRatchetKey;
+ this.counter = counter;
+ this.previousCounter = previousCounter;
+ this.ciphertext = ciphertext;
+ this.messageVersion = messageVersion;
+ }
+
+ public ECPublicKey getSenderRatchetKey() {
+ return senderRatchetKey;
+ }
+
+ public int getMessageVersion() {
+ return messageVersion;
+ }
+
+ public int getCounter() {
+ return counter;
+ }
+
+ public byte[] getBody() {
+ return ciphertext;
+ }
+
+ public void verifyMac(int messageVersion, IdentityKey senderIdentityKey,
+ IdentityKey receiverIdentityKey, SecretKeySpec macKey)
+ throws InvalidMessageException
+ {
+ byte[][] parts = ByteUtil.split(serialized, serialized.length - MAC_LENGTH, MAC_LENGTH);
+ byte[] ourMac = getMac(messageVersion, senderIdentityKey, receiverIdentityKey, macKey, parts[0]);
+ byte[] theirMac = parts[1];
+
+ if (!MessageDigest.isEqual(ourMac, theirMac)) {
+ throw new InvalidMessageException("Bad Mac!");
+ }
+ }
+
+ private byte[] getMac(int messageVersion,
+ IdentityKey senderIdentityKey,
+ IdentityKey receiverIdentityKey,
+ SecretKeySpec macKey, byte[] serialized)
+ {
+ try {
+ Mac mac = Mac.getInstance("HmacSHA256");
+ mac.init(macKey);
+
+ if (messageVersion >= 3) {
+ mac.update(senderIdentityKey.getPublicKey().serialize());
+ mac.update(receiverIdentityKey.getPublicKey().serialize());
+ }
+
+ byte[] fullMac = mac.doFinal(serialized);
+ return ByteUtil.trim(fullMac, MAC_LENGTH);
+ } catch (NoSuchAlgorithmException | java.security.InvalidKeyException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override
+ public byte[] serialize() {
+ return serialized;
+ }
+
+ @Override
+ public int getType() {
+ return CiphertextMessage.WHISPER_TYPE;
+ }
+
+ public static boolean isLegacy(byte[] message) {
+ return message != null && message.length >= 1 &&
+ ByteUtil.highBitsToInt(message[0]) <= CiphertextMessage.UNSUPPORTED_VERSION;
+ }
+
+}