From da45d6ad3d84472e6d223fb85b3cc2d4119884aa Mon Sep 17 00:00:00 2001 From: Derek Konigsberg Date: Tue, 28 Feb 2017 16:01:29 -0800 Subject: [PATCH] Handle the case of a missing group session signature key If we get into a state where the group session lacks a signature key, make sure the encrypt fails with an InvalidKeyException. This allows the code calling encrypt() to handle the condition by clearing the session. We can get into this state if we try to encrypt a message from a session that was previously used for decrypting messages. --- .../libsignal/groups/GroupCipher.java | 12 +++++- .../groups/state/SenderKeyState.java | 9 ++++- .../libsignal/groups/GroupCipherTest.java | 38 +++++++++++++++---- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/java/src/main/java/org/whispersystems/libsignal/groups/GroupCipher.java b/java/src/main/java/org/whispersystems/libsignal/groups/GroupCipher.java index d1d6bb60..687fd122 100644 --- a/java/src/main/java/org/whispersystems/libsignal/groups/GroupCipher.java +++ b/java/src/main/java/org/whispersystems/libsignal/groups/GroupCipher.java @@ -7,10 +7,12 @@ import org.whispersystems.libsignal.DecryptionCallback; import org.whispersystems.libsignal.DuplicateMessageException; +import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.libsignal.InvalidKeyIdException; import org.whispersystems.libsignal.InvalidMessageException; import org.whispersystems.libsignal.LegacyMessageException; import org.whispersystems.libsignal.NoSessionException; +import org.whispersystems.libsignal.ecc.ECPrivateKey; import org.whispersystems.libsignal.groups.ratchet.SenderChainKey; import org.whispersystems.libsignal.groups.ratchet.SenderMessageKey; import org.whispersystems.libsignal.groups.state.SenderKeyRecord; @@ -56,19 +58,25 @@ public GroupCipher(SenderKeyStore senderKeyStore, SenderKeyName senderKeyId) { * @param paddedPlaintext The plaintext message bytes, optionally padded. * @return Ciphertext. * @throws NoSessionException + * @throws InvalidKeyException */ - public byte[] encrypt(byte[] paddedPlaintext) throws NoSessionException { + public byte[] encrypt(byte[] paddedPlaintext) throws NoSessionException, InvalidKeyException { synchronized (LOCK) { try { SenderKeyRecord record = senderKeyStore.loadSenderKey(senderKeyId); SenderKeyState senderKeyState = record.getSenderKeyState(); SenderMessageKey senderKey = senderKeyState.getSenderChainKey().getSenderMessageKey(); + ECPrivateKey signatureKey = senderKeyState.getSigningKeyPrivate(); byte[] ciphertext = getCipherText(senderKey.getIv(), senderKey.getCipherKey(), paddedPlaintext); + if (signatureKey == null) { + throw new InvalidKeyException("Session missing signature key!"); + } + SenderKeyMessage senderKeyMessage = new SenderKeyMessage(senderKeyState.getKeyId(), senderKey.getIteration(), ciphertext, - senderKeyState.getSigningKeyPrivate()); + signatureKey); senderKeyState.setSenderChainKey(senderKeyState.getSenderChainKey().getNext()); diff --git a/java/src/main/java/org/whispersystems/libsignal/groups/state/SenderKeyState.java b/java/src/main/java/org/whispersystems/libsignal/groups/state/SenderKeyState.java index 5824d8b4..30b980ae 100644 --- a/java/src/main/java/org/whispersystems/libsignal/groups/state/SenderKeyState.java +++ b/java/src/main/java/org/whispersystems/libsignal/groups/state/SenderKeyState.java @@ -98,8 +98,13 @@ public ECPublicKey getSigningKeyPublic() throws InvalidKeyException { } public ECPrivateKey getSigningKeyPrivate() { - return Curve.decodePrivatePoint(senderKeyStateStructure.getSenderSigningKey() - .getPrivate().toByteArray()); + if (senderKeyStateStructure.hasSenderSigningKey() && + senderKeyStateStructure.getSenderSigningKey().hasPrivate()) { + return Curve.decodePrivatePoint(senderKeyStateStructure.getSenderSigningKey() + .getPrivate().toByteArray()); + } else { + return null; + } } public boolean hasSenderMessageKey(int iteration) { diff --git a/tests/src/test/java/org/whispersystems/libsignal/groups/GroupCipherTest.java b/tests/src/test/java/org/whispersystems/libsignal/groups/GroupCipherTest.java index 41082cd4..f96443ab 100644 --- a/tests/src/test/java/org/whispersystems/libsignal/groups/GroupCipherTest.java +++ b/tests/src/test/java/org/whispersystems/libsignal/groups/GroupCipherTest.java @@ -2,6 +2,7 @@ import junit.framework.TestCase; +import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.libsignal.SignalProtocolAddress; import org.whispersystems.libsignal.DuplicateMessageException; import org.whispersystems.libsignal.InvalidMessageException; @@ -22,7 +23,7 @@ public class GroupCipherTest extends TestCase { private static final SignalProtocolAddress SENDER_ADDRESS = new SignalProtocolAddress("+14150001111", 1); private static final SenderKeyName GROUP_SENDER = new SenderKeyName("nihilist history reading group", SENDER_ADDRESS); - public void testNoSession() throws InvalidMessageException, LegacyMessageException, NoSessionException, DuplicateMessageException { + public void testNoSession() throws InvalidMessageException, LegacyMessageException, NoSessionException, DuplicateMessageException, InvalidKeyException { InMemorySenderKeyStore aliceStore = new InMemorySenderKeyStore(); InMemorySenderKeyStore bobStore = new InMemorySenderKeyStore(); @@ -47,7 +48,7 @@ public void testNoSession() throws InvalidMessageException, LegacyMessageExcepti } public void testBasicEncryptDecrypt() - throws LegacyMessageException, DuplicateMessageException, InvalidMessageException, NoSessionException + throws LegacyMessageException, DuplicateMessageException, InvalidMessageException, NoSessionException, InvalidKeyException { InMemorySenderKeyStore aliceStore = new InMemorySenderKeyStore(); InMemorySenderKeyStore bobStore = new InMemorySenderKeyStore(); @@ -68,7 +69,7 @@ public void testBasicEncryptDecrypt() assertTrue(new String(plaintextFromAlice).equals("smert ze smert")); } - public void testLargeMessages() throws InvalidMessageException, LegacyMessageException, NoSessionException, DuplicateMessageException { + public void testLargeMessages() throws InvalidMessageException, LegacyMessageException, NoSessionException, DuplicateMessageException, InvalidKeyException { InMemorySenderKeyStore aliceStore = new InMemorySenderKeyStore(); InMemorySenderKeyStore bobStore = new InMemorySenderKeyStore(); @@ -92,7 +93,7 @@ public void testLargeMessages() throws InvalidMessageException, LegacyMessageExc } public void testBasicRatchet() - throws LegacyMessageException, DuplicateMessageException, InvalidMessageException, NoSessionException + throws LegacyMessageException, DuplicateMessageException, InvalidMessageException, NoSessionException, InvalidKeyException { InMemorySenderKeyStore aliceStore = new InMemorySenderKeyStore(); InMemorySenderKeyStore bobStore = new InMemorySenderKeyStore(); @@ -133,7 +134,7 @@ public void testBasicRatchet() assertTrue(new String(plaintextFromAlice3).equals("smert ze smert3")); } - public void testLateJoin() throws NoSessionException, InvalidMessageException, LegacyMessageException, DuplicateMessageException { + public void testLateJoin() throws NoSessionException, InvalidMessageException, LegacyMessageException, DuplicateMessageException, InvalidKeyException { InMemorySenderKeyStore aliceStore = new InMemorySenderKeyStore(); InMemorySenderKeyStore bobStore = new InMemorySenderKeyStore(); @@ -168,7 +169,7 @@ public void testLateJoin() throws NoSessionException, InvalidMessageException, L public void testOutOfOrder() - throws LegacyMessageException, DuplicateMessageException, InvalidMessageException, NoSessionException + throws LegacyMessageException, DuplicateMessageException, InvalidMessageException, NoSessionException, InvalidKeyException { InMemorySenderKeyStore aliceStore = new InMemorySenderKeyStore(); InMemorySenderKeyStore bobStore = new InMemorySenderKeyStore(); @@ -201,7 +202,7 @@ public void testOutOfOrder() } } - public void testEncryptNoSession() { + public void testEncryptNoSession() throws InvalidKeyException { InMemorySenderKeyStore aliceStore = new InMemorySenderKeyStore(); GroupCipher aliceGroupCipher = new GroupCipher(aliceStore, new SenderKeyName("coolio groupio", new SignalProtocolAddress("+10002223333", 1))); try { @@ -213,7 +214,7 @@ public void testEncryptNoSession() { } - public void testTooFarInFuture() throws DuplicateMessageException, InvalidMessageException, LegacyMessageException, NoSessionException { + public void testTooFarInFuture() throws DuplicateMessageException, InvalidMessageException, LegacyMessageException, NoSessionException, InvalidKeyException { InMemorySenderKeyStore aliceStore = new InMemorySenderKeyStore(); InMemorySenderKeyStore bobStore = new InMemorySenderKeyStore(); @@ -275,6 +276,27 @@ public void testMessageKeyLimit() throws Exception { } } + public void testInvalidSignatureKey() + throws LegacyMessageException, DuplicateMessageException, InvalidMessageException, NoSessionException + { + InMemorySenderKeyStore aliceStore = new InMemorySenderKeyStore(); + InMemorySenderKeyStore bobStore = new InMemorySenderKeyStore(); + + GroupSessionBuilder aliceSessionBuilder = new GroupSessionBuilder(aliceStore); + GroupSessionBuilder bobSessionBuilder = new GroupSessionBuilder(bobStore); + + GroupCipher bobGroupCipher = new GroupCipher(bobStore, GROUP_SENDER); + + SenderKeyDistributionMessage sentAliceDistributionMessage = aliceSessionBuilder.create(GROUP_SENDER); + SenderKeyDistributionMessage receivedAliceDistributionMessage = new SenderKeyDistributionMessage(sentAliceDistributionMessage.serialize()); + bobSessionBuilder.process(GROUP_SENDER, receivedAliceDistributionMessage); + + try { + bobGroupCipher.encrypt("smert ze smert".getBytes()); + } catch (InvalidKeyException e) { + // good + } + } private int randomInt() { try {