diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractOmemoIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractOmemoIntegrationTest.java index 0507fd278..225ffddb5 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractOmemoIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractOmemoIntegrationTest.java @@ -40,5 +40,8 @@ public abstract class AbstractOmemoIntegrationTest extends AbstractSmackIntegrat if (!OmemoService.isServiceRegistered()) { throw new TestNotPossibleException("No OmemoService registered."); } + + OmemoConfiguration.setCompleteSessionWithEmptyMessage(true); + OmemoConfiguration.setRepairBrokenSessionsWithPrekeyMessages(true); } } diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractOmemoMessageListener.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractOmemoMessageListener.java new file mode 100644 index 000000000..ded1a7fda --- /dev/null +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractOmemoMessageListener.java @@ -0,0 +1,149 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.omemo; + +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Stanza; +import org.jivesoftware.smackx.carbons.packet.CarbonExtension; +import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener; + +import org.igniterealtime.smack.inttest.util.ResultSyncPoint; +import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint; + +/** + * Convenience class. This listener is used so that implementers of OmemoMessageListener don't have to implement + * both messages. Instead they can just overwrite the message they want to implement. + */ +public class AbstractOmemoMessageListener implements OmemoMessageListener { + + @Override + public void onOmemoMessageReceived(Stanza stanza, OmemoMessage.Received decryptedMessage) { + // Override me + } + + @Override + public void onOmemoCarbonCopyReceived(CarbonExtension.Direction direction, Message carbonCopy, Message wrappingMessage, OmemoMessage.Received decryptedCarbonCopy) { + // Override me + } + + private static class SyncPointListener extends AbstractOmemoMessageListener { + protected final ResultSyncPoint syncPoint; + + public SyncPointListener(ResultSyncPoint syncPoint) { + this.syncPoint = syncPoint; + } + + public ResultSyncPoint getSyncPoint() { + return syncPoint; + } + } + + static class MessageListener extends SyncPointListener { + + protected final String expectedMessage; + + MessageListener(String expectedMessage, SimpleResultSyncPoint syncPoint) { + super(syncPoint); + this.expectedMessage = expectedMessage; + } + + MessageListener(String expectedMessage) { + this(expectedMessage, new SimpleResultSyncPoint()); + } + + @Override + public void onOmemoMessageReceived(Stanza stanza, OmemoMessage.Received received) { + SimpleResultSyncPoint srp = (SimpleResultSyncPoint) syncPoint; + if (received.isKeyTransportMessage()) { + return; + } + + if (received.getBody().equals(expectedMessage)) { + srp.signal(); + } else { + srp.signalFailure("Received decrypted message was not equal to sent message."); + } + } + } + + static class PreKeyMessageListener extends MessageListener { + PreKeyMessageListener(String expectedMessage, SimpleResultSyncPoint syncPoint) { + super(expectedMessage, syncPoint); + } + + PreKeyMessageListener(String expectedMessage) { + this(expectedMessage, new SimpleResultSyncPoint()); + } + + @Override + public void onOmemoMessageReceived(Stanza stanza, OmemoMessage.Received received) { + SimpleResultSyncPoint srp = (SimpleResultSyncPoint) syncPoint; + if (received.isKeyTransportMessage()) { + return; + } + + if (received.isPreKeyMessage()) { + if (received.getBody().equals(expectedMessage)) { + srp.signal(); + } else { + srp.signalFailure("Received decrypted message was not equal to sent message."); + } + } else { + srp.signalFailure("Received message was not a PreKeyMessage."); + } + } + } + + static class KeyTransportListener extends SyncPointListener { + + KeyTransportListener(SimpleResultSyncPoint resultSyncPoint) { + super(resultSyncPoint); + } + + KeyTransportListener() { + this(new SimpleResultSyncPoint()); + } + + @Override + public void onOmemoMessageReceived(Stanza stanza, OmemoMessage.Received received) { + SimpleResultSyncPoint s = (SimpleResultSyncPoint) syncPoint; + if (received.isKeyTransportMessage()) { + s.signal(); + } + } + } + + static class PreKeyKeyTransportListener extends KeyTransportListener { + PreKeyKeyTransportListener(SimpleResultSyncPoint resultSyncPoint) { + super(resultSyncPoint); + } + + PreKeyKeyTransportListener() { + this(new SimpleResultSyncPoint()); + } + + @Override + public void onOmemoMessageReceived(Stanza stanza, OmemoMessage.Received received) { + SimpleResultSyncPoint s = (SimpleResultSyncPoint) syncPoint; + if (received.isPreKeyMessage()) { + if (received.isKeyTransportMessage()) { + s.signal(); + } + } + } + } +} diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/MessageEncryptionIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/MessageEncryptionIntegrationTest.java index 5adab7817..25b50dd00 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/MessageEncryptionIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/MessageEncryptionIntegrationTest.java @@ -17,19 +17,15 @@ package org.jivesoftware.smackx.omemo; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPException; -import org.jivesoftware.smack.packet.Message; -import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smackx.omemo.element.OmemoBundleElement; -import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener; import org.igniterealtime.smack.inttest.SmackIntegrationTest; import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; import org.igniterealtime.smack.inttest.TestNotPossibleException; -import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint; /** * Simple OMEMO message encryption integration test. @@ -44,77 +40,71 @@ public class MessageEncryptionIntegrationTest extends AbstractTwoUsersOmemoInteg super(environment); } + /** + * This test checks whether the following actions are performed. + * + * Alice publishes bundle A1 + * Bob publishes bundle B1 + * + * Alice sends message to Bob (preKeyMessage) + * Bob publishes bundle B2 + * Alice still has A1 + * + * (Alice sends second message to bob to avoid race condition in the code (just for this example)) + * + * Bob responds to Alice (normal message) + * Alice still has A1 + * Bob still has B2 + * @throws Exception + */ @SmackIntegrationTest public void messageTest() throws Exception { - OmemoBundleElement aliceBundle1 = alice.getOmemoService().getOmemoStoreBackend().packOmemoBundle(alice.getOwnDevice()); - OmemoBundleElement bobsBundle1 = bob.getOmemoService().getOmemoStoreBackend().packOmemoBundle(bob.getOwnDevice()); + OmemoBundleElement a1 = alice.getOmemoService().getOmemoStoreBackend().packOmemoBundle(alice.getOwnDevice()); + OmemoBundleElement b1 = bob.getOmemoService().getOmemoStoreBackend().packOmemoBundle(bob.getOwnDevice()); - final String message1 = "One is greater than zero (for small values of zero)."; - OmemoMessage.Sent encrypted1 = alice.encrypt(bob.getOwnJid(), message1); - final SimpleResultSyncPoint bobReceivedMessage = new SimpleResultSyncPoint(); + // Alice sends message(s) to bob + // PreKeyMessage A -> B + final String body1 = "One is greater than zero (for small values of zero)."; + AbstractOmemoMessageListener.PreKeyMessageListener listener1 = + new AbstractOmemoMessageListener.PreKeyMessageListener(body1); + bob.addOmemoMessageListener(listener1); + OmemoMessage.Sent e1 = alice.encrypt(bob.getOwnJid(), body1); + alice.getConnection().sendStanza(e1.asMessage(bob.getOwnJid())); + listener1.getSyncPoint().waitForResult(10 * 1000); + bob.removeOmemoMessageListener(listener1); - bob.addOmemoMessageListener(new OmemoMessageListener() { - @Override - public void onOmemoMessageReceived(Stanza stanza, OmemoMessage.Received received) { - if (received.getMessage().equals(message1)) { - bobReceivedMessage.signal(); - } else { - bobReceivedMessage.signalFailure("Received decrypted message was not equal to sent message."); - } - } + // Message A -> B + final String body2 = "This message is sent to mitigate a race condition in the test"; + AbstractOmemoMessageListener.MessageListener listener2 = + new AbstractOmemoMessageListener.MessageListener(body2); + bob.addOmemoMessageListener(listener2); + OmemoMessage.Sent e2 = alice.encrypt(bob.getOwnJid(), body2); + alice.getConnection().sendStanza(e2.asMessage(bob.getOwnJid())); + listener2.getSyncPoint().waitForResult(10 * 1000); + bob.removeOmemoMessageListener(listener2); - @Override - public void onOmemoKeyTransportReceived(Stanza stanza, OmemoMessage.Received decryptedKeyTransportMessage) { - // Not needed - } + OmemoBundleElement a1_ = alice.getOmemoService().getOmemoStoreBackend().packOmemoBundle(alice.getOwnDevice()); + OmemoBundleElement b2 = bob.getOmemoService().getOmemoStoreBackend().packOmemoBundle(bob.getOwnDevice()); - }); + assertEquals("Alice sent bob a preKeyMessage, so her bundle MUST still be the same.", a1, a1_); + assertNotEquals("Bob just received a preKeyMessage from alice, so his bundle must have changed.", b1, b2); - Message m1 = new Message(); - m1.addExtension(encrypted1.getElement()); - m1.setTo(bob.getOwnJid()); - alice.getConnection().sendStanza(m1); - bobReceivedMessage.waitForResult(10 * 1000); + // Message B -> A + final String body3 = "The german words for 'leek' and 'wimp' are the same."; + AbstractOmemoMessageListener.MessageListener listener3 = + new AbstractOmemoMessageListener.MessageListener(body3); + alice.addOmemoMessageListener(listener3); + OmemoMessage.Sent e3 = bob.encrypt(alice.getOwnJid(), body3); + bob.getConnection().sendStanza(e3.asMessage(alice.getOwnJid())); + listener3.getSyncPoint().waitForResult(10 * 1000); + alice.removeOmemoMessageListener(listener3); - OmemoBundleElement aliceBundle2 = alice.getOmemoService().getOmemoStoreBackend().packOmemoBundle(alice.getOwnDevice()); - OmemoBundleElement bobsBundle2 = bob.getOmemoService().getOmemoStoreBackend().packOmemoBundle(bob.getOwnDevice()); + OmemoBundleElement a1__ = alice.getOmemoService().getOmemoStoreBackend().packOmemoBundle(alice.getOwnDevice()); + OmemoBundleElement b2_ = bob.getOmemoService().getOmemoStoreBackend().packOmemoBundle(bob.getOwnDevice()); - // Alice bundle is still the same, but bobs bundle changed, because he used up a pre-key. - assertEquals(aliceBundle1, aliceBundle2); - assertFalse(bobsBundle1.equals(bobsBundle2)); - - final String message2 = "The german words for 'leek' and 'wimp' are the same."; - final OmemoMessage.Sent encrypted2 = bob.encrypt(alice.getOwnJid(), message2); - final SimpleResultSyncPoint aliceReceivedMessage = new SimpleResultSyncPoint(); - - alice.addOmemoMessageListener(new OmemoMessageListener() { - @Override - public void onOmemoMessageReceived(Stanza stanza, OmemoMessage.Received received) { - if (received.getMessage().equals(message2)) { - aliceReceivedMessage.signal(); - } else { - aliceReceivedMessage.signalFailure("Received decrypted message was not equal to sent message."); - } - } - - @Override - public void onOmemoKeyTransportReceived(Stanza stanza, OmemoMessage.Received decryptedKeyTransportMessage) { - // Not needed - } - }); - - Message m2 = new Message(); - m2.addExtension(encrypted2.getElement()); - m2.setTo(alice.getOwnJid()); - bob.getConnection().sendStanza(m2); - aliceReceivedMessage.waitForResult(10 * 1000); - - OmemoBundleElement aliceBundle3 = alice.getOmemoService().getOmemoStoreBackend().packOmemoBundle(alice.getOwnDevice()); - OmemoBundleElement bobsBundle3 = bob.getOmemoService().getOmemoStoreBackend().packOmemoBundle(bob.getOwnDevice()); - - // Alice bundle did not change, because she already has a session with bob, which he initiated. - // Bobs bundle doesn't change this time. - assertEquals(aliceBundle2, aliceBundle3); - assertEquals(bobsBundle2, bobsBundle3); + assertEquals("Since alice initiated the session with bob, at no time he sent a preKeyMessage, " + + "so her bundle MUST still be the same.", a1_, a1__); + assertEquals("Bob changed his bundle earlier, but at this point his bundle must be equal to " + + "after the first change.", b2, b2_); } } diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/SessionRenegotiationIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/SessionRenegotiationIntegrationTest.java index fac791572..34d30ff92 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/SessionRenegotiationIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/SessionRenegotiationIntegrationTest.java @@ -16,21 +16,14 @@ */ package org.jivesoftware.smackx.omemo; -import static org.junit.Assert.fail; - -import java.util.concurrent.TimeoutException; +import java.util.logging.Level; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPException; -import org.jivesoftware.smack.packet.Message; -import org.jivesoftware.smack.packet.Stanza; -import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener; import org.igniterealtime.smack.inttest.SmackIntegrationTest; import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; import org.igniterealtime.smack.inttest.TestNotPossibleException; -import org.igniterealtime.smack.inttest.util.ResultSyncPoint; -import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint; public class SessionRenegotiationIntegrationTest extends AbstractTwoUsersOmemoIntegrationTest { @@ -40,130 +33,66 @@ public class SessionRenegotiationIntegrationTest extends AbstractTwoUsersOmemoIn super(environment); } - private static final String body1 = "P = NP is true for all N,P from the set of complex numbers, where P is equal to 0"; - private final SimpleResultSyncPoint bsp1 = new SimpleResultSyncPoint(); - private final OmemoMessageListener bml1 = new OmemoTestMessageListener(body1, bsp1); - - private static final String body2 = "P = NP is also true for all N,P from the set of complex numbers, where N is equal to 1."; - private final ResultSyncPoint bsp2 = new ResultSyncPoint<>(); - private final OmemoMessageListener bml2 = new OmemoMessageListener() { - @Override - public void onOmemoMessageReceived(Stanza stanza, OmemoMessage.Received received) { - if (received.getMessage().equals(body2)) { - bsp2.signal(new IllegalStateException("Message MUST NOT be decryptable!")); - } else { - bsp2.signal(new IllegalStateException("OmemoMessageListener MUST NOT be called for this message.")); - } - } - - @Override - public void onOmemoKeyTransportReceived(Stanza stanza, OmemoMessage.Received decryptedKeyTransportMessage) { - // Not needed - } - }; - private final SimpleResultSyncPoint asp2 = new SimpleResultSyncPoint(); - private final OmemoMessageListener aml2 = new OmemoMessageListener() { - - @Override - public void onOmemoMessageReceived(Stanza stanza, OmemoMessage.Received decryptedMessage) { - // Not needed - } - - @Override - public void onOmemoKeyTransportReceived(Stanza stanza, OmemoMessage.Received received) { - asp2.signal(); - } - }; - - private static final String body3 = "P = NP would be a disaster for the world of cryptography."; - private final SimpleResultSyncPoint bsp3 = new SimpleResultSyncPoint(); - private final OmemoMessageListener bml3 = new OmemoTestMessageListener(body3, bsp3); - @SmackIntegrationTest public void sessionRenegotiationTest() throws Exception { - /* - Send (PreKey-)message from Alice to Bob to initiate a session. - */ + + if (!OmemoConfiguration.getRepairBrokenSessionsWithPreKeyMessages()) { + throw new TestNotPossibleException("This test requires the property " + + "OmemoConfiguration.REPAIR_BROKEN_SESSIONS_WITH_PREKEY_MESSAGES " + + "set to 'true'."); + } + + final String body1 = "P = NP is true for all N,P from the set of complex numbers, where P is equal to 0"; + AbstractOmemoMessageListener.PreKeyMessageListener listener1 = + new AbstractOmemoMessageListener.PreKeyMessageListener(body1); OmemoMessage.Sent e1 = alice.encrypt(bob.getOwnJid(), body1); - Message m1 = new Message(); - m1.addExtension(e1.getElement()); - m1.setTo(bob.getOwnJid()); + bob.addOmemoMessageListener(listener1); + alice.getConnection().sendStanza(e1.asMessage(bob.getOwnJid())); + LOGGER.log(Level.INFO, "Message 1 sent"); + listener1.getSyncPoint().waitForResult(10 * 1000); + bob.removeOmemoMessageListener(listener1); - bob.addOmemoMessageListener(bml1); - alice.getConnection().sendStanza(m1); - bsp1.waitForResult(10 * 1000); - bob.removeOmemoMessageListener(bml1); + LOGGER.log(Level.INFO, "Message 1 received"); - /* - Delete the session records on Bobs side to render the session invalid. - */ + final String body2 = "This message is sent to circumvent a race condition in the test."; + AbstractOmemoMessageListener.MessageListener listener2 = new AbstractOmemoMessageListener.MessageListener(body2); + OmemoMessage.Sent e2 = alice.encrypt(bob.getOwnJid(), body2); + bob.addOmemoMessageListener(listener2); + alice.getConnection().sendStanza(e2.asMessage(bob.getOwnJid())); + LOGGER.log(Level.INFO, "Message 2 sent"); + listener2.getSyncPoint().waitForResult(10 * 1000); + bob.removeOmemoMessageListener(listener2); + + LOGGER.log(Level.INFO, "Message 2 received"); + + // Remove bobs session with alice. + + LOGGER.log(Level.INFO, "Delete session"); bob.getOmemoService().getOmemoStoreBackend().removeRawSession(bob.getOwnDevice(), alice.getOwnDevice()); - /* - Send normal message from Alice to Bob (Alice assumes, that Bob still has a valid session). - */ - OmemoMessage.Sent e2 = alice.encrypt(bob.getOwnJid(), body2); - Message m2 = new Message(); - m2.addExtension(e2.getElement()); - m2.setTo(bob.getOwnJid()); - - bob.addOmemoMessageListener(bml2); - alice.addOmemoMessageListener(aml2); - alice.getConnection().sendStanza(m2); - - /* - Wait for the timeout on Bobs side, since message decryption will fail now. - Bob will respond with an empty PreKeyMessage though, in order to repair the session. - */ - try { - bsp2.waitForResult(10 * 1000); - fail("This MUST throw a TimeoutException."); - } catch (IllegalStateException e) { - fail(e.getMessage()); - } catch (TimeoutException e) { - // Expected. - } - asp2.waitForResult(10 * 1000); - bob.removeOmemoMessageListener(bml2); - alice.removeOmemoMessageListener(aml2); - - /* - Since Bob responded with a PreKeyMessage to repair the broken session, Alice should now be able to send messages - which Bob can decrypt successfully again. - */ + final String body3 = "P = NP is also true for all N,P from the set of complex numbers, where N is equal to 1."; + AbstractOmemoMessageListener.PreKeyKeyTransportListener listener3 = + new AbstractOmemoMessageListener.PreKeyKeyTransportListener(); OmemoMessage.Sent e3 = alice.encrypt(bob.getOwnJid(), body3); - Message m3 = new Message(); - m3.addExtension(e3.getElement()); - m3.setTo(bob.getOwnJid()); + alice.addOmemoMessageListener(listener3); + alice.getConnection().sendStanza(e3.asMessage(bob.getOwnJid())); + LOGGER.log(Level.INFO, "Message 3 sent"); + listener3.getSyncPoint().waitForResult(10 * 1000); + alice.removeOmemoMessageListener(listener3); - bob.addOmemoMessageListener(bml3); - alice.getConnection().sendStanza(m3); - bsp3.waitForResult(10 * 1000); - bob.removeOmemoMessageListener(bml3); - } + LOGGER.log(Level.INFO, "Message 3 received"); - private static class OmemoTestMessageListener implements OmemoMessageListener { + final String body4 = "P = NP would be a disaster for the world of cryptography."; + AbstractOmemoMessageListener.MessageListener listener4 = new AbstractOmemoMessageListener.MessageListener(body4); + LOGGER.log(Level.INFO, "Attempt to encrypt message 4"); + OmemoMessage.Sent e4 = alice.encrypt(bob.getOwnJid(), body4); + LOGGER.log(Level.INFO, "Message 4 encrypted"); + bob.addOmemoMessageListener(listener4); + alice.getConnection().sendStanza(e4.asMessage(bob.getOwnJid())); + LOGGER.log(Level.INFO, "Message 4 sent"); + listener4.getSyncPoint().waitForResult(10 * 1000); + bob.removeOmemoMessageListener(listener4); - private final String expectedMessage; - private final SimpleResultSyncPoint syncPoint; - - OmemoTestMessageListener(String expectedMessage, SimpleResultSyncPoint syncPoint) { - this.expectedMessage = expectedMessage; - this.syncPoint = syncPoint; - } - - @Override - public void onOmemoMessageReceived(Stanza stanza, OmemoMessage.Received received) { - if (received.getMessage().equals(expectedMessage)) { - syncPoint.signal(); - } else { - syncPoint.signalFailure("Received decrypted message was not equal to sent message."); - } - } - - @Override - public void onOmemoKeyTransportReceived(Stanza stanza, OmemoMessage.Received decryptedKeyTransportMessage) { - - } + LOGGER.log(Level.INFO, "Message 4 received"); } } diff --git a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoManager.java b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoManager.java index 8835ffacf..30682a872 100644 --- a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoManager.java +++ b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoManager.java @@ -93,7 +93,7 @@ public final class OmemoManager extends Manager { private static final Logger LOGGER = Logger.getLogger(OmemoManager.class.getName()); private static final Integer UNKNOWN_DEVICE_ID = -1; - private final Object LOCK = new Object(); + final Object LOCK = new Object(); private static final WeakHashMap> INSTANCES = new WeakHashMap<>(); private final OmemoService service; @@ -613,18 +613,37 @@ public final class OmemoManager extends Manager { } } + /** + * Add an OmemoMessageListener. This listener will be informed about incoming OMEMO messages + * (as well as KeyTransportMessages) and OMEMO encrypted message carbons. + * + * @param listener OmemoMessageListener + */ public void addOmemoMessageListener(OmemoMessageListener listener) { omemoMessageListeners.add(listener); } + /** + * Remove an OmemoMessageListener. + * @param listener OmemoMessageListener + */ public void removeOmemoMessageListener(OmemoMessageListener listener) { omemoMessageListeners.remove(listener); } + /** + * Add an OmemoMucMessageListener. This listener will be informed about incoming OMEMO encrypted MUC messages. + * + * @param listener OmemoMessageListener. + */ public void addOmemoMucMessageListener(OmemoMucMessageListener listener) { omemoMucMessageListeners.add(listener); } + /** + * Remove an OmemoMucMessageListener. + * @param listener OmemoMucMessageListener + */ public void removeOmemoMucMessageListener(OmemoMucMessageListener listener) { omemoMucMessageListeners.remove(listener); } @@ -787,25 +806,12 @@ public final class OmemoManager extends Manager { } } - /** - * Notify all registered OmemoMessageListeners about a received OMEMO KeyTransportMessage. - * - * @param stanza original stanza. - * @param decryptedKeyTransportMessage decrypted KeyTransportMessage. - */ - void notifyOmemoKeyTransportMessageReceived(Stanza stanza, OmemoMessage.Received decryptedKeyTransportMessage) - { - for (OmemoMessageListener l : omemoMessageListeners) { - l.onOmemoKeyTransportReceived(stanza, decryptedKeyTransportMessage); - } - } - /** * Notify all registered OmemoMucMessageListeners of an incoming OmemoMessageElement in a MUC. * * @param muc MultiUserChat the message was received in. * @param stanza Original Stanza. - * @param decryptedMessage Decryped OmemoMessage. + * @param decryptedMessage Decrypted OmemoMessage. */ void notifyOmemoMucMessageReceived(MultiUserChat muc, Stanza stanza, @@ -817,18 +823,21 @@ public final class OmemoManager extends Manager { } /** - * Notify registered OmemoMucMessageReceived listeners about KeyTransportMessages sent in a MUC. + * Notify all registered OmemoMessageListeners of an incoming OMEMO encrypted Carbon Copy. + * Remember: If you want to receive OMEMO encrypted carbon copies, you have to enable carbons using + * {@link CarbonManager#enableCarbons()}. * - * @param muc MultiUserChat in which the message was sent - * @param stanza original stanza - * @param decryptedKeyTransportMessage decrypted OMEMO KeyTransportElement + * @param direction direction of the carbon copy + * @param carbonCopy carbon copy itself + * @param wrappingMessage wrapping message + * @param decryptedCarbonCopy decrypted carbon copy OMEMO element */ - void notifyOmemoMucKeyTransportMessageReceived(MultiUserChat muc, - Stanza stanza, - OmemoMessage.Received decryptedKeyTransportMessage) - { - for (OmemoMucMessageListener l : omemoMucMessageListeners) { - l.onOmemoKeyTransportReceived(muc, stanza, decryptedKeyTransportMessage); + void notifyOmemoCarbonCopyReceived(CarbonExtension.Direction direction, + Message carbonCopy, + Message wrappingMessage, + OmemoMessage.Received decryptedCarbonCopy) { + for (OmemoMessageListener l : omemoMessageListeners) { + l.onOmemoCarbonCopyReceived(direction, carbonCopy, wrappingMessage, decryptedCarbonCopy); } } diff --git a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoMessage.java b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoMessage.java index 5729cfb78..7fd935b6a 100644 --- a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoMessage.java +++ b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoMessage.java @@ -31,6 +31,8 @@ import org.jivesoftware.smackx.omemo.element.OmemoElement; import org.jivesoftware.smackx.omemo.internal.OmemoDevice; import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint; +import org.jxmpp.jid.Jid; + public class OmemoMessage { private final OmemoElement element; @@ -42,43 +44,91 @@ public class OmemoMessage { this.iv = iv; } + /** + * Return the original OmemoElement (<encrypted/>). + * + * @return omemoElement + */ public OmemoElement getElement() { return element; } + /** + * Return the messageKey (or transported key in case of a KeyTransportMessage). + * + * @return key + */ public byte[] getKey() { return messageKey.clone(); } + /** + * Return the initialization vector belonging to the key. + * @return initialization vector + */ public byte[] getIv() { return iv.clone(); } + /** + * Outgoing OMEMO message. + */ public static class Sent extends OmemoMessage { private final ArrayList intendedDevices = new ArrayList<>(); private final HashMap skippedDevices = new HashMap<>(); + /** + * Create a new outgoing OMEMO message. + * @param element OmemoElement + * @param key messageKey (or transported key) + * @param iv initialization vector belonging to key + * @param intendedDevices devices the client intended to encrypt the message for + * @param skippedDevices devices which were skipped during encryption process because encryption + * failed for some reason + */ Sent(OmemoElement element, byte[] key, byte[] iv, List intendedDevices, HashMap skippedDevices) { super(element, key, iv); this.intendedDevices.addAll(intendedDevices); this.skippedDevices.putAll(skippedDevices); } - public ArrayList getIntendedDevices() { + /** + * Return a list of all devices the sender originally intended to encrypt the message for. + * @return list of intended recipients. + */ + public List getIntendedDevices() { return intendedDevices; } + /** + * Return a map of all skipped recipients and the reasons for skipping. + * @return map of skipped recipients and reasons for that. + */ public HashMap getSkippedDevices() { return skippedDevices; } + /** + * Determine, if some recipients were skipped during encryption. + * @return true if recipients were skipped. + */ public boolean isMissingRecipients() { return !getSkippedDevices().isEmpty(); } - public Message asMessage() { + /** + * Return the OmemoElement wrapped in a Message ready to be sent. + * The message is addressed to recipient, contains the OmemoElement + * as well as an optional clear text hint as body, a MAM storage hint + * and an EME hint about OMEMO encryption. + * + * @param recipient recipient for the to-field of the message. + * @return Message + */ + public Message asMessage(Jid recipient) { Message messageStanza = new Message(); + messageStanza.setTo(recipient); messageStanza.addExtension(getElement()); if (OmemoConfiguration.getAddOmemoHintBody()) { @@ -92,54 +142,72 @@ public class OmemoMessage { } } + /** + * Incoming OMEMO message. + */ public static class Received extends OmemoMessage { private final String message; private final OmemoFingerprint sendersFingerprint; private final OmemoDevice senderDevice; - private final CARBON carbon; private final boolean preKeyMessage; - Received(OmemoElement element, byte[] key, byte[] iv, String message, OmemoFingerprint sendersFingerprint, OmemoDevice senderDevice, CARBON carbon, boolean preKeyMessage) { + /** + * Create a new incoming OMEMO message. + * @param element original OmemoElement + * @param key message key (or transported key) + * @param iv respective initialization vector + * @param body decrypted body + * @param sendersFingerprint OmemoFingerprint of the senders identityKey + * @param senderDevice OmemoDevice of the sender + * @param preKeyMessage if this was a preKeyMessage or not + */ + Received(OmemoElement element, byte[] key, byte[] iv, String body, OmemoFingerprint sendersFingerprint, OmemoDevice senderDevice, boolean preKeyMessage) { super(element, key, iv); - this.message = message; + this.message = body; this.sendersFingerprint = sendersFingerprint; this.senderDevice = senderDevice; - this.carbon = carbon; this.preKeyMessage = preKeyMessage; } - public String getMessage() { + /** + * Return the decrypted body of the message. + * @return decrypted body + */ + public String getBody() { return message; } + /** + * Return the fingerprint of the messages sender device. + * @return fingerprint of sender + */ public OmemoFingerprint getSendersFingerprint() { return sendersFingerprint; } + /** + * Return the OmemoDevice which sent the message. + * @return senderDevice + */ public OmemoDevice getSenderDevice() { return senderDevice; } /** - * Return the carbon type. - * - * @return carbon type + * Return true, if this message was sent as a preKeyMessage. + * @return preKeyMessage or not */ - public CARBON getCarbon() { - return carbon; - } - boolean isPreKeyMessage() { return preKeyMessage; } - } - /** - * Types of Carbon Messages. - */ - public enum CARBON { - NONE, //No carbon - SENT, //Sent carbon - RECV //Received Carbon + /** + * Return true, if the message was a KeyTransportMessage. + * A KeyTransportMessage is a OmemoMessage without a payload. + * @return keyTransportMessage? + */ + public boolean isKeyTransportMessage() { + return message == null; + } } } diff --git a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoService.java b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoService.java index cf6223bb2..a8c647b09 100644 --- a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoService.java +++ b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoService.java @@ -435,8 +435,7 @@ public abstract class OmemoService devices) { + static void removeOurDevice(OmemoDevice userDevice, Collection devices) { if (devices.contains(userDevice)) { devices.remove(userDevice); } @@ -1074,67 +1073,138 @@ public abstract class OmemoService devices = new HashSet<>(); + devices.add(a); devices.add(b); + + assertTrue(devices.contains(a)); + assertTrue(devices.contains(b)); + OmemoService.removeOurDevice(a, devices); + + assertFalse(devices.contains(a)); + assertTrue(devices.contains(b)); } }