From bf9c8708e0dc961e01d8901cfd8b290cf0947703 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Tue, 17 Mar 2015 10:57:31 -0700 Subject: [PATCH] Make it possible to deserialize SenderKeyDistributionMessages --- .../SenderKeyDistributionMessage.java | 52 ++++++++++++++++--- .../libaxolotl/groups/GroupCipherTest.java | 46 ++++++++++++++-- 2 files changed, 87 insertions(+), 11 deletions(-) diff --git a/java/src/main/java/org/whispersystems/libaxolotl/protocol/SenderKeyDistributionMessage.java b/java/src/main/java/org/whispersystems/libaxolotl/protocol/SenderKeyDistributionMessage.java index 424dd87cd..35f9e2181 100644 --- a/java/src/main/java/org/whispersystems/libaxolotl/protocol/SenderKeyDistributionMessage.java +++ b/java/src/main/java/org/whispersystems/libaxolotl/protocol/SenderKeyDistributionMessage.java @@ -1,7 +1,12 @@ package org.whispersystems.libaxolotl.protocol; import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +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; @@ -15,17 +20,52 @@ public class SenderKeyDistributionMessage implements CiphertextMessage { public SenderKeyDistributionMessage(int id, int iteration, byte[] chainKey, ECPublicKey signatureKey) { byte[] version = {ByteUtil.intsToByteHighAndLow(CURRENT_VERSION, CURRENT_VERSION)}; + byte[] protobuf = WhisperProtos.SenderKeyDistributionMessage.newBuilder() + .setId(id) + .setIteration(iteration) + .setChainKey(ByteString.copyFrom(chainKey)) + .setSigningKey(ByteString.copyFrom(signatureKey.serialize())) + .build().toByteArray(); this.id = id; this.iteration = iteration; this.chainKey = chainKey; this.signatureKey = signatureKey; - this.serialized = WhisperProtos.SenderKeyDistributionMessage.newBuilder() - .setId(id) - .setIteration(iteration) - .setChainKey(ByteString.copyFrom(chainKey)) - .setSigningKey(ByteString.copyFrom(signatureKey.serialize())) - .build().toByteArray(); + this.serialized = ByteUtil.combine(version, protobuf); + } + + public SenderKeyDistributionMessage(byte[] serialized) throws LegacyMessageException, InvalidMessageException { + try { + byte[][] messageParts = ByteUtil.split(serialized, 1, serialized.length - 1); + byte version = messageParts[0][0]; + byte[] message = messageParts[1]; + + if (ByteUtil.highBitsToInt(version) < CiphertextMessage.CURRENT_VERSION) { + throw new LegacyMessageException("Legacy message: " + ByteUtil.highBitsToInt(version)); + } + + if (ByteUtil.highBitsToInt(version) > CURRENT_VERSION) { + throw new InvalidMessageException("Unknown version: " + ByteUtil.highBitsToInt(version)); + } + + WhisperProtos.SenderKeyDistributionMessage distributionMessage = WhisperProtos.SenderKeyDistributionMessage.parseFrom(message); + + if (!distributionMessage.hasId() || + !distributionMessage.hasIteration() || + !distributionMessage.hasChainKey() || + !distributionMessage.hasSigningKey()) + { + throw new InvalidMessageException("Incomplete message."); + } + + this.serialized = serialized; + this.id = distributionMessage.getId(); + this.iteration = distributionMessage.getIteration(); + this.chainKey = distributionMessage.getChainKey().toByteArray(); + this.signatureKey = Curve.decodePoint(distributionMessage.getSigningKey().toByteArray(), 0); + } catch (InvalidProtocolBufferException | InvalidKeyException e) { + throw new InvalidMessageException(e); + } } @Override diff --git a/tests/src/test/java/org/whispersystems/libaxolotl/groups/GroupCipherTest.java b/tests/src/test/java/org/whispersystems/libaxolotl/groups/GroupCipherTest.java index dd5a306fd..32fcc652a 100644 --- a/tests/src/test/java/org/whispersystems/libaxolotl/groups/GroupCipherTest.java +++ b/tests/src/test/java/org/whispersystems/libaxolotl/groups/GroupCipherTest.java @@ -30,9 +30,9 @@ public class GroupCipherTest extends TestCase { GroupCipher aliceGroupCipher = new GroupCipher(aliceStore, GROUP_SENDER); GroupCipher bobGroupCipher = new GroupCipher(bobStore, GROUP_SENDER); - SenderKeyDistributionMessage aliceDistributionMessage = aliceSessionBuilder.create(GROUP_SENDER); - - bobSessionBuilder.process(GROUP_SENDER, aliceDistributionMessage); + SenderKeyDistributionMessage sentAliceDistributionMessage = aliceSessionBuilder.create(GROUP_SENDER); + SenderKeyDistributionMessage receivedAliceDistributionMessage = new SenderKeyDistributionMessage(sentAliceDistributionMessage.serialize()); + bobSessionBuilder.process(GROUP_SENDER, receivedAliceDistributionMessage); byte[] ciphertextFromAlice = aliceGroupCipher.encrypt("smert ze smert".getBytes()); byte[] plaintextFromAlice = bobGroupCipher.decrypt(ciphertextFromAlice); @@ -54,10 +54,12 @@ public class GroupCipherTest extends TestCase { GroupCipher aliceGroupCipher = new GroupCipher(aliceStore, aliceName); GroupCipher bobGroupCipher = new GroupCipher(bobStore, aliceName); - SenderKeyDistributionMessage aliceDistributionMessage = + SenderKeyDistributionMessage sentAliceDistributionMessage = aliceSessionBuilder.create(aliceName); + SenderKeyDistributionMessage receivedAliceDistributionMessage = + new SenderKeyDistributionMessage(sentAliceDistributionMessage.serialize()); - bobSessionBuilder.process(aliceName, aliceDistributionMessage); + bobSessionBuilder.process(aliceName, receivedAliceDistributionMessage); byte[] ciphertextFromAlice = aliceGroupCipher.encrypt("smert ze smert".getBytes()); byte[] ciphertextFromAlice2 = aliceGroupCipher.encrypt("smert ze smert2".getBytes()); @@ -80,6 +82,40 @@ public class GroupCipherTest extends TestCase { assertTrue(new String(plaintextFromAlice3).equals("smert ze smert3")); } + public void testLateJoin() throws NoSessionException, InvalidMessageException, LegacyMessageException, DuplicateMessageException { + InMemorySenderKeyStore aliceStore = new InMemorySenderKeyStore(); + InMemorySenderKeyStore bobStore = new InMemorySenderKeyStore(); + + GroupSessionBuilder aliceSessionBuilder = new GroupSessionBuilder(aliceStore); + + + SenderKeyName aliceName = GROUP_SENDER; + + GroupCipher aliceGroupCipher = new GroupCipher(aliceStore, aliceName); + + + SenderKeyDistributionMessage aliceDistributionMessage = aliceSessionBuilder.create(aliceName); + // Send off to some people. + + for (int i=0;i<100;i++) { + aliceGroupCipher.encrypt("up the punks up the punks up the punks".getBytes()); + } + + // Now Bob Joins. + GroupSessionBuilder bobSessionBuilder = new GroupSessionBuilder(bobStore); + GroupCipher bobGroupCipher = new GroupCipher(bobStore, aliceName); + + + SenderKeyDistributionMessage distributionMessageToBob = aliceSessionBuilder.create(aliceName); + bobSessionBuilder.process(aliceName, new SenderKeyDistributionMessage(distributionMessageToBob.serialize())); + + byte[] ciphertext = aliceGroupCipher.encrypt("welcome to the group".getBytes()); + byte[] plaintext = bobGroupCipher.decrypt(ciphertext); + + assertEquals(new String(plaintext), "welcome to the group"); + } + + public void testOutOfOrder() throws LegacyMessageException, DuplicateMessageException, InvalidMessageException, NoSessionException {