diff options
author | Moxie Marlinspike <moxie@thoughtcrime.org> | 2014-11-24 12:54:30 -0800 |
---|---|---|
committer | Moxie Marlinspike <moxie@thoughtcrime.org> | 2014-11-24 12:54:30 -0800 |
commit | 60800e155612bea797eed93c67046a23d26054cc (patch) | |
tree | d88368c1c26162e27e790195133ca2b526597afe /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.java | 172 |
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; + } + +} |