mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2025-09-09 10:19:41 +02:00
Rework support for XEP-0384: OMEMO Encryption
Changes: Rework integration tests New structure of base integration test classes bump dependency on signal-protocol-java from 2.4.0 to 2.6.2 Introduced CachingOmemoStore implementations Use CachingOmemoStore classes in integration tests Removed OmemoSession classes (replaced with more logical OmemoRatchet classes) Consequently also removed load/storeOmemoSession methods from OmemoStore Removed some clutter from KeyUtil classes Moved trust decision related code from OmemoStore to TrustCallback Require authenticated connection for many functions Add async initialization function in OmemoStore Refactor omemo test package (/java/org/jivesoftware/smack/omemo -> /java/org/jivesoftware/smackx) Remove OmemoStore method isFreshInstallation() as well as defaultDeviceId related stuff FileBasedOmemoStore: Add cleaner methods to store/load base data types (Using tryWithResource, only for future releases, once Android API gets bumped) Attempt to make OmemoManager thread safe new logic for getInstanceFor() deviceId determination OmemoManagers encrypt methods now don't throw exceptions when encryption for some devices fails. Instead message gets encrypted when possible and more information about failures gets returned alongside the message itself Added OmemoMessage class for that purpose Reworked entire OmemoService class Use safer logic for creating trust-ignoring messages (like ratchet-update messages) Restructure elements/provider in order to prepare for OMEMO namespace bumps Remove OmemoManager.regenerate() methods in favor of getInstanceFor(connection, randomDeviceId) Removed some unnecessary configuration options Prepare for support of more AES message key types Simplify session creation Where possible, avoid side effects in methods Add UntrustedOmemoIdentityException Add TrustState enum More improved tests
This commit is contained in:
parent
f290197f6a
commit
1f731f6318
96 changed files with 6915 additions and 5488 deletions
|
@ -13,6 +13,7 @@ dependencies {
|
|||
compile project(':smack-experimental')
|
||||
compile project(':smack-omemo')
|
||||
compile project(':smack-debug')
|
||||
compile project(path: ":smack-omemo", configuration: "testRuntime")
|
||||
compile 'org.reflections:reflections:0.9.9-RC1'
|
||||
compile 'eu.geekplace.javapinning:java-pinning-java7:1.1.0-alpha1'
|
||||
// Note that the junit-vintage-engine runtime dependency is not
|
||||
|
|
|
@ -16,40 +16,21 @@
|
|||
*/
|
||||
package org.jivesoftware.smackx.omemo;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
|
||||
import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
|
||||
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
|
||||
import org.igniterealtime.smack.inttest.TestNotPossibleException;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
/**
|
||||
* Super class for OMEMO integration tests.
|
||||
*/
|
||||
public abstract class AbstractOmemoIntegrationTest extends AbstractSmackIntegrationTest {
|
||||
|
||||
private static final File storePath;
|
||||
|
||||
static {
|
||||
String userHome = System.getProperty("user.home");
|
||||
if (userHome != null) {
|
||||
File f = new File(userHome);
|
||||
storePath = new File(f, ".config/smack-integration-test/store");
|
||||
} else {
|
||||
storePath = new File("int_test_omemo_store");
|
||||
}
|
||||
}
|
||||
|
||||
public AbstractOmemoIntegrationTest(SmackIntegrationTestEnvironment environment) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, TestNotPossibleException {
|
||||
super(environment);
|
||||
if (OmemoConfiguration.getFileBasedOmemoStoreDefaultPath() == null) {
|
||||
OmemoConfiguration.setFileBasedOmemoStoreDefaultPath(storePath);
|
||||
}
|
||||
|
||||
// Test for server support
|
||||
if (!OmemoManager.serverSupportsOmemo(connection, connection.getXMPPServiceDomain())) {
|
||||
throw new TestNotPossibleException("Server does not support OMEMO (PubSub)");
|
||||
|
@ -59,23 +40,8 @@ public abstract class AbstractOmemoIntegrationTest extends AbstractSmackIntegrat
|
|||
if (!OmemoService.isServiceRegistered()) {
|
||||
throw new TestNotPossibleException("No OmemoService registered.");
|
||||
}
|
||||
|
||||
OmemoConfiguration.setCompleteSessionWithEmptyMessage(true);
|
||||
OmemoConfiguration.setRepairBrokenSessionsWithPrekeyMessages(true);
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public void beforeTest() {
|
||||
LOGGER.log(Level.INFO, "START EXECUTION");
|
||||
OmemoIntegrationTestHelper.deletePath(storePath);
|
||||
before();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public void afterTest() {
|
||||
after();
|
||||
OmemoIntegrationTestHelper.deletePath(storePath);
|
||||
LOGGER.log(Level.INFO, "END EXECUTION");
|
||||
}
|
||||
|
||||
public abstract void before();
|
||||
|
||||
public abstract void after();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017 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 static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
|
||||
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
|
||||
import org.igniterealtime.smack.inttest.TestNotPossibleException;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
/**
|
||||
* Abstract OMEMO integration test framing, which creates and initializes two OmemoManagers (for conOne and conTwo).
|
||||
* Both users subscribe to one another and trust their identities.
|
||||
* After the test traces in PubSub and in the users Rosters are removed.
|
||||
*/
|
||||
public abstract class AbstractTwoUsersOmemoIntegrationTest extends AbstractOmemoIntegrationTest {
|
||||
|
||||
protected OmemoManager alice, bob;
|
||||
|
||||
public AbstractTwoUsersOmemoIntegrationTest(SmackIntegrationTestEnvironment environment)
|
||||
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
|
||||
SmackException.NoResponseException, TestNotPossibleException {
|
||||
super(environment);
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public void setup() throws Exception {
|
||||
alice = OmemoManagerSetupHelper.prepareOmemoManager(conOne);
|
||||
bob = OmemoManagerSetupHelper.prepareOmemoManager(conTwo);
|
||||
|
||||
LOGGER.log(Level.FINE, "Alice: " + alice.getOwnDevice() + " Bob: " + bob.getOwnDevice());
|
||||
assertFalse(alice.getDeviceId().equals(bob.getDeviceId()));
|
||||
|
||||
// Subscribe presences
|
||||
OmemoManagerSetupHelper.syncSubscribePresence(alice.getConnection(), bob.getConnection(), "bob", null);
|
||||
OmemoManagerSetupHelper.syncSubscribePresence(bob.getConnection(), alice.getConnection(), "alice", null);
|
||||
|
||||
OmemoManagerSetupHelper.trustAllIdentitiesWithTests(alice, bob); // Alice trusts Bob's devices
|
||||
OmemoManagerSetupHelper.trustAllIdentitiesWithTests(bob, alice); // Bob trusts Alice' and Mallory's devices
|
||||
|
||||
assertEquals(bob.getOwnFingerprint(), alice.getActiveFingerprints(bob.getOwnJid()).get(bob.getOwnDevice()));
|
||||
assertEquals(alice.getOwnFingerprint(), bob.getActiveFingerprints(alice.getOwnJid()).get(alice.getOwnDevice()));
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public void cleanUp() {
|
||||
alice.stopStanzaAndPEPListeners();
|
||||
bob.stopStanzaAndPEPListeners();
|
||||
OmemoManagerSetupHelper.cleanUpPubSub(alice);
|
||||
OmemoManagerSetupHelper.cleanUpRoster(alice);
|
||||
OmemoManagerSetupHelper.cleanUpPubSub(bob);
|
||||
OmemoManagerSetupHelper.cleanUpRoster(bob);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017 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 static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smackx.omemo.element.OmemoBundleElement;
|
||||
|
||||
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
|
||||
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
|
||||
import org.igniterealtime.smack.inttest.TestNotPossibleException;
|
||||
|
||||
/**
|
||||
* Simple OMEMO message encryption integration test.
|
||||
* During this test Alice sends an encrypted message to Bob. Bob decrypts it and sends a response to Alice.
|
||||
* It is checked whether the messages can be decrypted, and if used up pre-keys result in renewed bundles.
|
||||
*/
|
||||
public class MessageEncryptionIntegrationTest extends AbstractTwoUsersOmemoIntegrationTest {
|
||||
|
||||
public MessageEncryptionIntegrationTest(SmackIntegrationTestEnvironment environment)
|
||||
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
|
||||
SmackException.NoResponseException, TestNotPossibleException {
|
||||
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
|
||||
*
|
||||
* Bob responds to Alice (normal message)
|
||||
* Alice still has A1
|
||||
* Bob still has B2
|
||||
* @throws Exception
|
||||
*/
|
||||
@SmackIntegrationTest
|
||||
public void messageTest() throws Exception {
|
||||
OmemoBundleElement a1 = alice.getOmemoService().getOmemoStoreBackend().packOmemoBundle(alice.getOwnDevice());
|
||||
OmemoBundleElement b1 = bob.getOmemoService().getOmemoStoreBackend().packOmemoBundle(bob.getOwnDevice());
|
||||
|
||||
// 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);
|
||||
|
||||
OmemoBundleElement a1_ = alice.getOmemoService().getOmemoStoreBackend().packOmemoBundle(alice.getOwnDevice());
|
||||
OmemoBundleElement b2;
|
||||
|
||||
synchronized (bob.LOCK) { // Circumvent race condition where bundle gets replenished after getting stored in b2
|
||||
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 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 a1__ = alice.getOmemoService().getOmemoStoreBackend().packOmemoBundle(alice.getOwnDevice());
|
||||
OmemoBundleElement b2_ = bob.getOmemoService().getOmemoStoreBackend().packOmemoBundle(bob.getOwnDevice());
|
||||
|
||||
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_);
|
||||
}
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017 Florian Schmaus, 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 static junit.framework.TestCase.assertNotNull;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.cleanServerSideTraces;
|
||||
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.setUpOmemoManager;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||
|
||||
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
|
||||
import org.jivesoftware.smackx.omemo.util.OmemoConstants;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubException;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubException.NotAPubSubNodeException;
|
||||
|
||||
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
|
||||
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
|
||||
import org.igniterealtime.smack.inttest.TestNotPossibleException;
|
||||
|
||||
public class OmemoInitializationTest extends AbstractOmemoIntegrationTest {
|
||||
|
||||
private OmemoManager alice;
|
||||
private OmemoStore<?,?,?,?,?,?,?,?,?> store;
|
||||
|
||||
@Override
|
||||
public void before() {
|
||||
alice = OmemoManager.getInstanceFor(conOne, 666);
|
||||
store = OmemoService.getInstance().getOmemoStoreBackend();
|
||||
}
|
||||
|
||||
public OmemoInitializationTest(SmackIntegrationTestEnvironment environment) throws TestNotPossibleException, XMPPErrorException, NotConnectedException, NoResponseException, InterruptedException {
|
||||
super(environment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests, if the initialization is done properly.
|
||||
* @throws NotAPubSubNodeException
|
||||
*/
|
||||
@SmackIntegrationTest
|
||||
public void initializationTest() throws XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, SmackException.NotLoggedInException, CorruptedOmemoKeyException, NotAPubSubNodeException {
|
||||
// test keys.
|
||||
setUpOmemoManager(alice);
|
||||
assertNotNull("IdentityKey must not be null after initialization.", store.loadOmemoIdentityKeyPair(alice));
|
||||
assertTrue("We must have " + OmemoConstants.TARGET_PRE_KEY_COUNT + " preKeys.",
|
||||
store.loadOmemoPreKeys(alice).size() == OmemoConstants.TARGET_PRE_KEY_COUNT);
|
||||
assertNotNull("Our signedPreKey must not be null.", store.loadCurrentSignedPreKeyId(alice));
|
||||
|
||||
// Is deviceId published?
|
||||
assertTrue("Published deviceList must contain our deviceId.",
|
||||
OmemoService.fetchDeviceList(alice, alice.getOwnJid())
|
||||
.getDeviceIds().contains(alice.getDeviceId()));
|
||||
|
||||
assertTrue("Our fingerprint must be of correct length.",
|
||||
OmemoService.getInstance().getOmemoStoreBackend().getFingerprint(alice).length() == 64);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void after() {
|
||||
alice.shutdown();
|
||||
cleanServerSideTraces(alice);
|
||||
}
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017 Florian Schmaus, 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 static junit.framework.TestCase.assertEquals;
|
||||
import static junit.framework.TestCase.assertNotNull;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.roster.Roster;
|
||||
import org.jivesoftware.smack.roster.RosterEntry;
|
||||
|
||||
import org.jivesoftware.smackx.omemo.element.OmemoBundleElement;
|
||||
import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException;
|
||||
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
|
||||
import org.jivesoftware.smackx.omemo.internal.CachedDeviceList;
|
||||
import org.jivesoftware.smackx.omemo.util.OmemoConstants;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubException;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubException.NotAPubSubNodeException;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubManager;
|
||||
|
||||
/**
|
||||
* Class containing some helper methods for OmemoIntegrationTests.
|
||||
*/
|
||||
final class OmemoIntegrationTestHelper {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(OmemoIntegrationTestHelper.class.getSimpleName());
|
||||
|
||||
static void cleanServerSideTraces(OmemoManager omemoManager) {
|
||||
cleanUpPubSub(omemoManager);
|
||||
cleanUpRoster(omemoManager);
|
||||
}
|
||||
|
||||
static void deletePath(File storePath) {
|
||||
FileBasedOmemoStore.deleteDirectory(storePath);
|
||||
}
|
||||
|
||||
static void deletePath(OmemoManager omemoManager) {
|
||||
OmemoService.getInstance().getOmemoStoreBackend().purgeOwnDeviceKeys(omemoManager);
|
||||
}
|
||||
|
||||
static void cleanUpPubSub(OmemoManager omemoManager) {
|
||||
PubSubManager pm = PubSubManager.getInstance(omemoManager.getConnection(),omemoManager.getOwnJid());
|
||||
try {
|
||||
omemoManager.requestDeviceListUpdateFor(omemoManager.getOwnJid());
|
||||
} catch (SmackException.NotConnectedException | InterruptedException | SmackException.NoResponseException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
CachedDeviceList deviceList = OmemoService.getInstance().getOmemoStoreBackend().loadCachedDeviceList(omemoManager, omemoManager.getOwnJid());
|
||||
for (int id : deviceList.getAllDevices()) {
|
||||
try {
|
||||
pm.getLeafNode(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(id)).deleteAllItems();
|
||||
} catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException | NotAPubSubNodeException e) {
|
||||
// Silent
|
||||
}
|
||||
|
||||
try {
|
||||
pm.deleteNode(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(id));
|
||||
} catch (SmackException.NoResponseException | InterruptedException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) {
|
||||
// Silent
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
pm.getLeafNode(OmemoConstants.PEP_NODE_DEVICE_LIST).deleteAllItems();
|
||||
} catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException | NotAPubSubNodeException e) {
|
||||
// Silent
|
||||
}
|
||||
|
||||
try {
|
||||
pm.deleteNode(OmemoConstants.PEP_NODE_DEVICE_LIST);
|
||||
} catch (SmackException.NoResponseException | InterruptedException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) {
|
||||
// Silent
|
||||
}
|
||||
}
|
||||
|
||||
static void cleanUpRoster(OmemoManager omemoManager) {
|
||||
Roster roster = Roster.getInstanceFor(omemoManager.getConnection());
|
||||
for (RosterEntry r : roster.getEntries()) {
|
||||
try {
|
||||
roster.removeEntry(r);
|
||||
} catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException | SmackException.NotLoggedInException e) {
|
||||
// Silent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Let Alice subscribe to Bob.
|
||||
* @param alice
|
||||
* @param bob
|
||||
* @throws SmackException.NotLoggedInException
|
||||
* @throws XMPPException.XMPPErrorException
|
||||
* @throws SmackException.NotConnectedException
|
||||
* @throws InterruptedException
|
||||
* @throws SmackException.NoResponseException
|
||||
*/
|
||||
static void subscribe(OmemoManager alice, OmemoManager bob, String nick)
|
||||
throws SmackException.NotLoggedInException, XMPPException.XMPPErrorException,
|
||||
SmackException.NotConnectedException, InterruptedException,
|
||||
SmackException.NoResponseException {
|
||||
|
||||
Roster aliceRoster = Roster.getInstanceFor(alice.getConnection());
|
||||
Roster bobsRoster = Roster.getInstanceFor(bob.getConnection());
|
||||
bobsRoster.setSubscriptionMode(Roster.SubscriptionMode.accept_all);
|
||||
aliceRoster.createEntry(bob.getOwnJid(), nick, null);
|
||||
}
|
||||
|
||||
|
||||
static void unidirectionalTrust(OmemoManager alice, OmemoManager bob) throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, CannotEstablishOmemoSessionException {
|
||||
// Fetch deviceList
|
||||
alice.requestDeviceListUpdateFor(bob.getOwnJid());
|
||||
LOGGER.log(Level.INFO, "Current deviceList state: " + alice.getOwnDevice() + " knows " + bob.getOwnDevice() + ": "
|
||||
+ OmemoService.getInstance().getOmemoStoreBackend().loadCachedDeviceList(alice, bob.getOwnJid()));
|
||||
assertTrue("Trusting party must know the others device at this point.",
|
||||
alice.getOmemoService().getOmemoStoreBackend().loadCachedDeviceList(alice, bob.getOwnJid())
|
||||
.getActiveDevices().contains(bob.getDeviceId()));
|
||||
|
||||
// Create sessions
|
||||
alice.buildSessionsWith(bob.getOwnJid());
|
||||
assertTrue("Trusting party must have a session with the other end at this point.",
|
||||
!alice.getOmemoService().getOmemoStoreBackend().loadAllRawSessionsOf(alice, bob.getOwnJid()).isEmpty());
|
||||
|
||||
// Trust the other party
|
||||
alice.getOmemoService().getOmemoStoreBackend().trustOmemoIdentity(alice, bob.getOwnDevice(),
|
||||
alice.getOmemoService().getOmemoStoreBackend().getFingerprint(alice, bob.getOwnDevice()));
|
||||
|
||||
}
|
||||
|
||||
static void setUpOmemoManager(OmemoManager omemoManager) throws CorruptedOmemoKeyException, InterruptedException, SmackException.NoResponseException, SmackException.NotConnectedException, XMPPException.XMPPErrorException, SmackException.NotLoggedInException, PubSubException.NotALeafNodeException, NotAPubSubNodeException {
|
||||
omemoManager.initialize();
|
||||
OmemoBundleElement bundle = OmemoService.fetchBundle(omemoManager, omemoManager.getOwnDevice());
|
||||
assertNotNull("Bundle must not be null.", bundle);
|
||||
assertEquals("Published Bundle must equal our local bundle.", bundle, omemoManager.getOmemoService().getOmemoStoreBackend().packOmemoBundle(omemoManager));
|
||||
}
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017 Florian Schmaus, 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 static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.cleanServerSideTraces;
|
||||
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.setUpOmemoManager;
|
||||
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.subscribe;
|
||||
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.unidirectionalTrust;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.chat2.ChatManager;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
|
||||
import org.jivesoftware.smackx.omemo.element.OmemoElement;
|
||||
import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
|
||||
import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation;
|
||||
import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener;
|
||||
import org.jivesoftware.smackx.omemo.util.OmemoMessageBuilder;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Test keyTransportMessages.
|
||||
*/
|
||||
public class OmemoKeyTransportTest extends AbstractOmemoIntegrationTest {
|
||||
|
||||
private OmemoManager alice, bob;
|
||||
|
||||
public OmemoKeyTransportTest(SmackIntegrationTestEnvironment environment) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, TestNotPossibleException {
|
||||
super(environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void before() {
|
||||
alice = OmemoManager.getInstanceFor(conOne, 11111);
|
||||
bob = OmemoManager.getInstanceFor(conTwo, 222222);
|
||||
}
|
||||
|
||||
@SmackIntegrationTest
|
||||
public void keyTransportTest() throws Exception {
|
||||
final SimpleResultSyncPoint syncPoint = new SimpleResultSyncPoint();
|
||||
|
||||
subscribe(alice, bob, "Bob");
|
||||
subscribe(bob, alice, "Alice");
|
||||
|
||||
setUpOmemoManager(alice);
|
||||
setUpOmemoManager(bob);
|
||||
|
||||
unidirectionalTrust(alice, bob);
|
||||
unidirectionalTrust(bob, alice);
|
||||
|
||||
final byte[] key = OmemoMessageBuilder.generateKey();
|
||||
final byte[] iv = OmemoMessageBuilder.generateIv();
|
||||
|
||||
bob.addOmemoMessageListener(new OmemoMessageListener() {
|
||||
@Override
|
||||
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
|
||||
// Don't care
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
|
||||
LOGGER.log(Level.INFO, "Received a keyTransportMessage.");
|
||||
assertTrue("Key must match the one we sent.", Arrays.equals(key, cipherAndAuthTag.getKey()));
|
||||
assertTrue("IV must match the one we sent.", Arrays.equals(iv, cipherAndAuthTag.getIv()));
|
||||
syncPoint.signal();
|
||||
}
|
||||
});
|
||||
|
||||
OmemoElement keyTransportElement = alice.createKeyTransportElement(key, iv, bob.getOwnDevice());
|
||||
Message message = new Message(bob.getOwnJid());
|
||||
message.addExtension(keyTransportElement);
|
||||
ChatManager.getInstanceFor(alice.getConnection()).chatWith(bob.getOwnJid().asEntityBareJidIfPossible())
|
||||
.send(message);
|
||||
|
||||
// TODO: Should use 'timeout' field instead of hardcoded '10 * 1000'.
|
||||
syncPoint.waitForResult(10 * 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void after() {
|
||||
alice.shutdown();
|
||||
bob.shutdown();
|
||||
cleanServerSideTraces(alice);
|
||||
cleanServerSideTraces(bob);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
*
|
||||
* 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 static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smackx.mam.MamManager;
|
||||
import org.jivesoftware.smackx.mam.element.MamPrefsIQ;
|
||||
import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
|
||||
import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException;
|
||||
import org.jivesoftware.smackx.omemo.util.MessageOrOmemoMessage;
|
||||
|
||||
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
|
||||
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
|
||||
import org.igniterealtime.smack.inttest.TestNotPossibleException;
|
||||
|
||||
/**
|
||||
* This test sends a message from Alice to Bob, while Bob has automatic decryption disabled.
|
||||
* Then Bob fetches his Mam archive and decrypts the result.
|
||||
*/
|
||||
public class OmemoMamDecryptionTest extends AbstractTwoUsersOmemoIntegrationTest {
|
||||
public OmemoMamDecryptionTest(SmackIntegrationTestEnvironment environment)
|
||||
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
|
||||
SmackException.NoResponseException, TestNotPossibleException {
|
||||
super(environment);
|
||||
MamManager bobsMamManager = MamManager.getInstanceFor(conTwo);
|
||||
if (!bobsMamManager.isSupported()) {
|
||||
throw new TestNotPossibleException("Test is not possible, because MAM is not supported on the server.");
|
||||
}
|
||||
}
|
||||
|
||||
@SmackIntegrationTest
|
||||
public void mamDecryptionTest() throws XMPPException.XMPPErrorException, SmackException.NotLoggedInException,
|
||||
SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException,
|
||||
CryptoFailedException, UndecidedOmemoIdentityException {
|
||||
// Make sure, Bobs server stores messages in the archive
|
||||
MamManager bobsMamManager = MamManager.getInstanceFor(bob.getConnection());
|
||||
bobsMamManager.enableMamForAllMessages();
|
||||
bobsMamManager.setDefaultBehavior(MamPrefsIQ.DefaultBehavior.always);
|
||||
|
||||
// Prevent bob from automatically decrypting MAM messages.
|
||||
bob.stopStanzaAndPEPListeners();
|
||||
|
||||
String body = "This message will be stored in MAM!";
|
||||
OmemoMessage.Sent encrypted = alice.encrypt(bob.getOwnJid(), body);
|
||||
alice.getConnection().sendStanza(encrypted.asMessage(bob.getOwnJid()));
|
||||
|
||||
MamManager.MamQuery query = bobsMamManager.queryArchive(MamManager.MamQueryArgs.builder().limitResultsToJid(alice.getOwnJid()).build());
|
||||
assertEquals(1, query.getMessageCount());
|
||||
|
||||
List<MessageOrOmemoMessage> decryptedMamQuery = bob.decryptMamQueryResult(query);
|
||||
|
||||
assertEquals(1, decryptedMamQuery.size());
|
||||
assertEquals(body, decryptedMamQuery.get(decryptedMamQuery.size() - 1).getOmemoMessage().getBody());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017 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 static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.packet.Presence;
|
||||
import org.jivesoftware.smack.roster.PresenceEventListener;
|
||||
import org.jivesoftware.smack.roster.Roster;
|
||||
import org.jivesoftware.smack.roster.RosterEntry;
|
||||
import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException;
|
||||
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
|
||||
import org.jivesoftware.smackx.omemo.internal.OmemoCachedDeviceList;
|
||||
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
|
||||
import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint;
|
||||
import org.jivesoftware.smackx.omemo.util.EphemeralTrustCallback;
|
||||
import org.jivesoftware.smackx.omemo.util.OmemoConstants;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubException;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubManager;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.jxmpp.jid.FullJid;
|
||||
import org.jxmpp.jid.Jid;
|
||||
|
||||
|
||||
public class OmemoManagerSetupHelper {
|
||||
|
||||
/**
|
||||
* Synchronously subscribes presence.
|
||||
* @param subscriber connection of user which subscribes.
|
||||
* @param target connection of user which gets subscribed.
|
||||
* @param targetNick nick of the subscribed user.
|
||||
* @param targetGroups groups of the user.
|
||||
* @throws Exception
|
||||
*/
|
||||
public static void syncSubscribePresence(final XMPPConnection subscriber,
|
||||
final XMPPConnection target,
|
||||
String targetNick,
|
||||
String[] targetGroups)
|
||||
throws Exception {
|
||||
final SimpleResultSyncPoint subscribed = new SimpleResultSyncPoint();
|
||||
|
||||
Roster subscriberRoster = Roster.getInstanceFor(subscriber);
|
||||
Roster targetRoster = Roster.getInstanceFor(target);
|
||||
|
||||
targetRoster.setSubscriptionMode(Roster.SubscriptionMode.accept_all);
|
||||
subscriberRoster.addPresenceEventListener(new PresenceEventListener() {
|
||||
@Override
|
||||
public void presenceAvailable(FullJid address, Presence availablePresence) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void presenceUnavailable(FullJid address, Presence presence) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void presenceError(Jid address, Presence errorPresence) {
|
||||
subscribed.signalFailure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void presenceSubscribed(BareJid address, Presence subscribedPresence) {
|
||||
subscribed.signal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void presenceUnsubscribed(BareJid address, Presence unsubscribedPresence) {
|
||||
}
|
||||
});
|
||||
|
||||
subscriberRoster.createEntry(target.getUser().asBareJid(), targetNick, targetGroups);
|
||||
|
||||
subscribed.waitForResult(10 * 1000);
|
||||
}
|
||||
|
||||
public static void trustAllIdentities(OmemoManager alice, OmemoManager bob)
|
||||
throws InterruptedException, SmackException.NotConnectedException, SmackException.NotLoggedInException,
|
||||
SmackException.NoResponseException, CannotEstablishOmemoSessionException, CorruptedOmemoKeyException,
|
||||
XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException {
|
||||
Roster roster = Roster.getInstanceFor(alice.getConnection());
|
||||
|
||||
if (alice.getOwnJid() != bob.getOwnJid() &&
|
||||
(!roster.iAmSubscribedTo(bob.getOwnJid()) || !roster.isSubscribedToMyPresence(bob.getOwnJid()))) {
|
||||
throw new IllegalStateException("Before trusting identities of a user, we must be subscribed to one another.");
|
||||
}
|
||||
|
||||
alice.requestDeviceListUpdateFor(bob.getOwnJid());
|
||||
HashMap<OmemoDevice, OmemoFingerprint> fingerprints = alice.getActiveFingerprints(bob.getOwnJid());
|
||||
|
||||
for (OmemoDevice device : fingerprints.keySet()) {
|
||||
OmemoFingerprint fingerprint = fingerprints.get(device);
|
||||
alice.trustOmemoIdentity(device, fingerprint);
|
||||
}
|
||||
}
|
||||
|
||||
public static void trustAllIdentitiesWithTests(OmemoManager alice, OmemoManager bob)
|
||||
throws InterruptedException, SmackException.NotConnectedException, SmackException.NotLoggedInException,
|
||||
SmackException.NoResponseException, CannotEstablishOmemoSessionException, CorruptedOmemoKeyException,
|
||||
XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException {
|
||||
alice.requestDeviceListUpdateFor(bob.getOwnJid());
|
||||
HashMap<OmemoDevice, OmemoFingerprint> fps1 = alice.getActiveFingerprints(bob.getOwnJid());
|
||||
|
||||
assertFalse(fps1.isEmpty());
|
||||
assertAllDevicesAreUndecided(alice, fps1);
|
||||
assertAllDevicesAreUntrusted(alice, fps1);
|
||||
|
||||
trustAllIdentities(alice, bob);
|
||||
|
||||
HashMap<OmemoDevice, OmemoFingerprint> fps2 = alice.getActiveFingerprints(bob.getOwnJid());
|
||||
assertEquals(fps1.size(), fps2.size());
|
||||
assertTrue(Maps.difference(fps1, fps2).areEqual());
|
||||
|
||||
assertAllDevicesAreDecided(alice, fps2);
|
||||
assertAllDevicesAreTrusted(alice, fps2);
|
||||
}
|
||||
|
||||
public static OmemoManager prepareOmemoManager(XMPPConnection connection) throws Exception {
|
||||
final OmemoManager manager = OmemoManager.getInstanceFor(connection, OmemoManager.randomDeviceId());
|
||||
manager.setTrustCallback(new EphemeralTrustCallback());
|
||||
|
||||
if (connection.isAuthenticated()) {
|
||||
manager.initialize();
|
||||
} else {
|
||||
throw new AssertionError("Connection must be authenticated.");
|
||||
}
|
||||
return manager;
|
||||
}
|
||||
|
||||
public static void assertAllDevicesAreUndecided(OmemoManager manager, HashMap<OmemoDevice, OmemoFingerprint> devices) {
|
||||
for (OmemoDevice device : devices.keySet()) {
|
||||
// All fingerprints MUST be neither decided, nor trusted.
|
||||
assertFalse(manager.isDecidedOmemoIdentity(device, devices.get(device)));
|
||||
}
|
||||
}
|
||||
|
||||
public static void assertAllDevicesAreUntrusted(OmemoManager manager, HashMap<OmemoDevice, OmemoFingerprint> devices) {
|
||||
for (OmemoDevice device : devices.keySet()) {
|
||||
// All fingerprints MUST be neither decided, nor trusted.
|
||||
assertFalse(manager.isTrustedOmemoIdentity(device, devices.get(device)));
|
||||
}
|
||||
}
|
||||
|
||||
public static void assertAllDevicesAreDecided(OmemoManager manager, HashMap<OmemoDevice, OmemoFingerprint> devices) {
|
||||
for (OmemoDevice device : devices.keySet()) {
|
||||
// All fingerprints MUST be neither decided, nor trusted.
|
||||
assertTrue(manager.isDecidedOmemoIdentity(device, devices.get(device)));
|
||||
}
|
||||
}
|
||||
|
||||
public static void assertAllDevicesAreTrusted(OmemoManager manager, HashMap<OmemoDevice, OmemoFingerprint> devices) {
|
||||
for (OmemoDevice device : devices.keySet()) {
|
||||
// All fingerprints MUST be neither decided, nor trusted.
|
||||
assertTrue(manager.isTrustedOmemoIdentity(device, devices.get(device)));
|
||||
}
|
||||
}
|
||||
|
||||
public static void cleanUpPubSub(OmemoManager omemoManager) {
|
||||
PubSubManager pm = PubSubManager.getInstance(omemoManager.getConnection(),omemoManager.getOwnJid());
|
||||
try {
|
||||
omemoManager.requestDeviceListUpdateFor(omemoManager.getOwnJid());
|
||||
} catch (SmackException.NotConnectedException | InterruptedException | SmackException.NoResponseException | PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
OmemoCachedDeviceList deviceList = OmemoService.getInstance().getOmemoStoreBackend()
|
||||
.loadCachedDeviceList(omemoManager.getOwnDevice(), omemoManager.getOwnJid());
|
||||
|
||||
for (int id : deviceList.getAllDevices()) {
|
||||
try {
|
||||
pm.getLeafNode(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(id)).deleteAllItems();
|
||||
} catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException |
|
||||
PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException |
|
||||
PubSubException.NotAPubSubNodeException e) {
|
||||
// Silent
|
||||
}
|
||||
|
||||
try {
|
||||
pm.deleteNode(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(id));
|
||||
} catch (SmackException.NoResponseException | InterruptedException | SmackException.NotConnectedException
|
||||
| XMPPException.XMPPErrorException e) {
|
||||
// Silent
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
pm.getLeafNode(OmemoConstants.PEP_NODE_DEVICE_LIST).deleteAllItems();
|
||||
} catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException |
|
||||
PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException |
|
||||
PubSubException.NotAPubSubNodeException e) {
|
||||
// Silent
|
||||
}
|
||||
|
||||
try {
|
||||
pm.deleteNode(OmemoConstants.PEP_NODE_DEVICE_LIST);
|
||||
} catch (SmackException.NoResponseException | InterruptedException | SmackException.NotConnectedException |
|
||||
XMPPException.XMPPErrorException e) {
|
||||
// Silent
|
||||
}
|
||||
}
|
||||
|
||||
public static void cleanUpRoster(OmemoManager omemoManager) {
|
||||
Roster roster = Roster.getInstanceFor(omemoManager.getConnection());
|
||||
for (RosterEntry r : roster.getEntries()) {
|
||||
try {
|
||||
roster.removeEntry(r);
|
||||
} catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException |
|
||||
XMPPException.XMPPErrorException | SmackException.NotLoggedInException e) {
|
||||
// Silent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,193 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017 Florian Schmaus, 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 static junit.framework.TestCase.assertEquals;
|
||||
import static junit.framework.TestCase.assertNotSame;
|
||||
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.cleanServerSideTraces;
|
||||
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.setUpOmemoManager;
|
||||
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.subscribe;
|
||||
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.unidirectionalTrust;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.chat2.ChatManager;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
|
||||
import org.jivesoftware.smackx.omemo.element.OmemoBundleElement;
|
||||
import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException;
|
||||
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
|
||||
import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
|
||||
import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException;
|
||||
import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
|
||||
import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation;
|
||||
import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubException;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubException.NotAPubSubNodeException;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Test message sending.
|
||||
*/
|
||||
public class OmemoMessageSendingTest extends AbstractOmemoIntegrationTest {
|
||||
|
||||
private OmemoManager alice, bob;
|
||||
private OmemoStore<?,?,?,?,?,?,?,?,?> store;
|
||||
|
||||
public OmemoMessageSendingTest(SmackIntegrationTestEnvironment environment) throws XMPPException.XMPPErrorException, TestNotPossibleException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
|
||||
super(environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void before() {
|
||||
alice = OmemoManager.getInstanceFor(conOne, 123);
|
||||
bob = OmemoManager.getInstanceFor(conTwo, 345);
|
||||
store = OmemoService.getInstance().getOmemoStoreBackend();
|
||||
}
|
||||
|
||||
/**
|
||||
* This Test tests sending and receiving messages.
|
||||
* Alice and Bob create fresh devices, then they add another to their rosters.
|
||||
* Next they build sessions with one another and Alice sends a message to Bob.
|
||||
* After receiving and successfully decrypting the message, its tested, if Bob
|
||||
* publishes a new Bundle. After that Bob replies to the message and its tested,
|
||||
* whether Alice can decrypt the message and if she does NOT publish a new Bundle.
|
||||
*
|
||||
* @throws CorruptedOmemoKeyException
|
||||
* @throws InterruptedException
|
||||
* @throws SmackException.NoResponseException
|
||||
* @throws SmackException.NotConnectedException
|
||||
* @throws XMPPException.XMPPErrorException
|
||||
* @throws SmackException.NotLoggedInException
|
||||
* @throws PubSubException.NotALeafNodeException
|
||||
* @throws CannotEstablishOmemoSessionException
|
||||
* @throws UndecidedOmemoIdentityException
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws CryptoFailedException
|
||||
* @throws NotAPubSubNodeException
|
||||
*/
|
||||
@SmackIntegrationTest
|
||||
public void messageSendingTest()
|
||||
throws CorruptedOmemoKeyException, InterruptedException, SmackException.NoResponseException,
|
||||
SmackException.NotConnectedException, XMPPException.XMPPErrorException,
|
||||
SmackException.NotLoggedInException, PubSubException.NotALeafNodeException,
|
||||
CannotEstablishOmemoSessionException, UndecidedOmemoIdentityException, NoSuchAlgorithmException,
|
||||
CryptoFailedException, PubSubException.NotAPubSubNodeException {
|
||||
final String alicesSecret = "Hey Bob! I love you!";
|
||||
final String bobsSecret = "I love you too, Alice."; //aww <3
|
||||
|
||||
final SimpleResultSyncPoint messageOneSyncPoint = new SimpleResultSyncPoint();
|
||||
final SimpleResultSyncPoint messageTwoSyncPoint = new SimpleResultSyncPoint();
|
||||
|
||||
// Subscribe to one another
|
||||
subscribe(alice, bob, "Bob");
|
||||
subscribe(bob, alice,"Alice");
|
||||
|
||||
// initialize OmemoManagers
|
||||
setUpOmemoManager(alice);
|
||||
setUpOmemoManager(bob);
|
||||
|
||||
// Save initial bundles
|
||||
OmemoBundleElement aliceBundle = store.packOmemoBundle(alice);
|
||||
OmemoBundleElement bobsBundle = store.packOmemoBundle(bob);
|
||||
|
||||
// Trust
|
||||
unidirectionalTrust(alice, bob);
|
||||
unidirectionalTrust(bob, alice);
|
||||
|
||||
// Register messageListeners
|
||||
bob.addOmemoMessageListener(new OmemoMessageListener() {
|
||||
@Override
|
||||
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
|
||||
LOGGER.log(Level.INFO,"Bob received message: " + decryptedBody);
|
||||
if (decryptedBody.trim().equals(alicesSecret.trim())) {
|
||||
messageOneSyncPoint.signal();
|
||||
} else {
|
||||
messageOneSyncPoint.signal(new Exception("Received message must equal sent message."));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
|
||||
}
|
||||
});
|
||||
|
||||
alice.addOmemoMessageListener(new OmemoMessageListener() {
|
||||
@Override
|
||||
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
|
||||
LOGGER.log(Level.INFO, "Alice received message: " + decryptedBody);
|
||||
if (decryptedBody.trim().equals(bobsSecret.trim())) {
|
||||
messageTwoSyncPoint.signal();
|
||||
} else {
|
||||
messageTwoSyncPoint.signal(new Exception("Received message must equal sent message."));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// Prepare Alice message for Bob
|
||||
Message encryptedA = alice.encrypt(bob.getOwnJid(), alicesSecret);
|
||||
ChatManager.getInstanceFor(alice.getConnection()).chatWith(bob.getOwnJid().asEntityBareJidIfPossible())
|
||||
.send(encryptedA);
|
||||
|
||||
try {
|
||||
messageOneSyncPoint.waitForResult(10 * 1000);
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.SEVERE, "Exception while waiting for message: " + e, e);
|
||||
TestCase.fail("Bob must have received Alice message.");
|
||||
}
|
||||
|
||||
// Check if Bob published a new Bundle
|
||||
assertNotSame("Bob must have published another bundle at this point, since we used a PreKeyMessage.",
|
||||
bobsBundle, OmemoService.fetchBundle(alice, bob.getOwnDevice()));
|
||||
|
||||
// Prepare Bobs response
|
||||
Message encryptedB = bob.encrypt(alice.getOwnJid(), bobsSecret);
|
||||
ChatManager.getInstanceFor(bob.getConnection()).chatWith(alice.getOwnJid().asEntityBareJidIfPossible())
|
||||
.send(encryptedB);
|
||||
|
||||
try {
|
||||
messageTwoSyncPoint.waitForResult(10 * 1000);
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.SEVERE, "Exception while waiting for response: " + e, e);
|
||||
TestCase.fail("Alice must have received a response from Bob.");
|
||||
}
|
||||
|
||||
assertEquals("Alice must not have published a new bundle, since we built the session using Bobs bundle.",
|
||||
aliceBundle, OmemoService.fetchBundle(bob, alice.getOwnDevice()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void after() {
|
||||
alice.shutdown();
|
||||
bob.shutdown();
|
||||
cleanServerSideTraces(alice);
|
||||
cleanServerSideTraces(bob);
|
||||
}
|
||||
}
|
|
@ -1,195 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017 Florian Schmaus, 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 static junit.framework.TestCase.assertEquals;
|
||||
import static junit.framework.TestCase.fail;
|
||||
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.cleanServerSideTraces;
|
||||
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.setUpOmemoManager;
|
||||
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.subscribe;
|
||||
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.unidirectionalTrust;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.chat2.ChatManager;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
|
||||
import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
|
||||
import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Test session renegotiation.
|
||||
*/
|
||||
public class OmemoSessionRenegotiationTest extends AbstractOmemoIntegrationTest {
|
||||
|
||||
private OmemoManager alice, bob;
|
||||
private OmemoStore<?,?,?,?,?,?,?,?,?> store;
|
||||
|
||||
public OmemoSessionRenegotiationTest(SmackIntegrationTestEnvironment environment) throws XMPPException.XMPPErrorException, TestNotPossibleException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
|
||||
super(environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void before() {
|
||||
alice = OmemoManager.getInstanceFor(conOne, 1337);
|
||||
bob = OmemoManager.getInstanceFor(conTwo, 1009);
|
||||
store = OmemoService.getInstance().getOmemoStoreBackend();
|
||||
}
|
||||
|
||||
@SmackIntegrationTest
|
||||
public void sessionRenegotiationTest() throws Exception {
|
||||
|
||||
final boolean[] phaseTwo = new boolean[1];
|
||||
final SimpleResultSyncPoint sp1 = new SimpleResultSyncPoint();
|
||||
final SimpleResultSyncPoint sp2 = new SimpleResultSyncPoint();
|
||||
final SimpleResultSyncPoint sp3 = new SimpleResultSyncPoint();
|
||||
final SimpleResultSyncPoint sp4 = new SimpleResultSyncPoint();
|
||||
|
||||
final String m1 = "1: Alice says hello to bob.";
|
||||
final String m2 = "2: Bob replies to Alice.";
|
||||
final String m3 = "3. This message will arrive but Bob cannot decrypt it.";
|
||||
final String m4 = "4. This message is readable by Bob again.";
|
||||
|
||||
subscribe(alice, bob, "Bob");
|
||||
subscribe(bob, alice, "Alice");
|
||||
|
||||
setUpOmemoManager(alice);
|
||||
setUpOmemoManager(bob);
|
||||
|
||||
unidirectionalTrust(alice, bob);
|
||||
unidirectionalTrust(bob, alice);
|
||||
|
||||
OmemoMessageListener first = new OmemoMessageListener() {
|
||||
@Override
|
||||
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
|
||||
LOGGER.log(Level.INFO, "Bob received OMEMO message: " + decryptedBody);
|
||||
assertEquals("Received message MUST match the one we sent.", decryptedBody, m1);
|
||||
sp1.signal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
|
||||
|
||||
}
|
||||
};
|
||||
bob.addOmemoMessageListener(first);
|
||||
|
||||
ChatManager.getInstanceFor(alice.getConnection()).chatWith(bob.getOwnJid().asEntityBareJidIfPossible())
|
||||
.send(alice.encrypt(bob.getOwnJid(), m1));
|
||||
|
||||
sp1.waitForResult(10 * 1000);
|
||||
|
||||
bob.removeOmemoMessageListener(first);
|
||||
|
||||
OmemoMessageListener second = new OmemoMessageListener() {
|
||||
@Override
|
||||
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
|
||||
LOGGER.log(Level.INFO, "Alice received OMEMO message: " + decryptedBody);
|
||||
assertEquals("Reply must match the message we sent.", decryptedBody, m2);
|
||||
sp2.signal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
|
||||
|
||||
}
|
||||
};
|
||||
alice.addOmemoMessageListener(second);
|
||||
|
||||
ChatManager.getInstanceFor(bob.getConnection()).chatWith(alice.getOwnJid().asEntityBareJidIfPossible())
|
||||
.send(bob.encrypt(alice.getOwnJid(), m2));
|
||||
|
||||
sp2.waitForResult(10 * 1000);
|
||||
|
||||
alice.removeOmemoMessageListener(second);
|
||||
|
||||
store.forgetOmemoSessions(bob);
|
||||
store.removeAllRawSessionsOf(bob, alice.getOwnJid());
|
||||
|
||||
OmemoMessageListener third = new OmemoMessageListener() {
|
||||
@Override
|
||||
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
|
||||
fail("Bob should not have received a decipherable message: " + decryptedBody);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
|
||||
|
||||
}
|
||||
};
|
||||
bob.addOmemoMessageListener(third);
|
||||
|
||||
OmemoMessageListener fourth = new OmemoMessageListener() {
|
||||
@Override
|
||||
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
|
||||
LOGGER.log(Level.INFO, "Alice received preKeyMessage.");
|
||||
sp3.signal();
|
||||
}
|
||||
};
|
||||
alice.addOmemoMessageListener(fourth);
|
||||
|
||||
ChatManager.getInstanceFor(alice.getConnection()).chatWith(bob.getOwnJid().asEntityBareJidIfPossible())
|
||||
.send(alice.encrypt(bob.getOwnJid(), m3));
|
||||
|
||||
sp3.waitForResult(10 * 1000);
|
||||
|
||||
bob.removeOmemoMessageListener(third);
|
||||
alice.removeOmemoMessageListener(fourth);
|
||||
|
||||
OmemoMessageListener fifth = new OmemoMessageListener() {
|
||||
@Override
|
||||
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
|
||||
LOGGER.log(Level.INFO, "Bob received an OMEMO message: " + decryptedBody);
|
||||
assertEquals("The received message must match the one we sent.",
|
||||
decryptedBody, m4);
|
||||
sp4.signal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
|
||||
|
||||
}
|
||||
};
|
||||
bob.addOmemoMessageListener(fifth);
|
||||
|
||||
ChatManager.getInstanceFor(alice.getConnection()).chatWith(bob.getOwnJid().asEntityBareJidIfPossible())
|
||||
.send(alice.encrypt(bob.getOwnJid(), m4));
|
||||
|
||||
sp4.waitForResult(10 * 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void after() {
|
||||
alice.shutdown();
|
||||
bob.shutdown();
|
||||
cleanServerSideTraces(alice);
|
||||
cleanServerSideTraces(bob);
|
||||
}
|
||||
}
|
|
@ -1,163 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017 Florian Schmaus, 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 static junit.framework.TestCase.assertEquals;
|
||||
import static junit.framework.TestCase.assertFalse;
|
||||
import static junit.framework.TestCase.assertNotNull;
|
||||
import static junit.framework.TestCase.assertNotSame;
|
||||
import static junit.framework.TestCase.assertNull;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.cleanServerSideTraces;
|
||||
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.deletePath;
|
||||
import static org.jivesoftware.smackx.omemo.OmemoIntegrationTestHelper.setUpOmemoManager;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
|
||||
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
|
||||
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
|
||||
import org.igniterealtime.smack.inttest.TestNotPossibleException;
|
||||
|
||||
/**
|
||||
* Test the OmemoStore.
|
||||
*/
|
||||
public class OmemoStoreTest extends AbstractOmemoIntegrationTest {
|
||||
|
||||
private OmemoManager alice;
|
||||
private OmemoManager bob;
|
||||
|
||||
public OmemoStoreTest(SmackIntegrationTestEnvironment environment) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, TestNotPossibleException {
|
||||
super(environment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void before() {
|
||||
alice = OmemoManager.getInstanceFor(conOne);
|
||||
bob = OmemoManager.getInstanceFor(conOne);
|
||||
}
|
||||
|
||||
@SmackIntegrationTest
|
||||
public void storeTest() throws Exception {
|
||||
|
||||
// ########### PRE-INITIALIZATION ############
|
||||
|
||||
assertEquals("Creating an OmemoManager without MUST have set the default deviceId.", alice.getDeviceId(), OmemoService.getInstance().getOmemoStoreBackend().getDefaultDeviceId(alice.getOwnJid()));
|
||||
assertEquals("OmemoManager must be equal, since both got created without giving a deviceId.", alice, bob);
|
||||
OmemoService.getInstance().getOmemoStoreBackend().setDefaultDeviceId(alice.getOwnJid(), -1); //Reset default deviceId
|
||||
|
||||
alice.shutdown();
|
||||
|
||||
alice = OmemoManager.getInstanceFor(conOne);
|
||||
assertNotSame("Instantiating OmemoManager without deviceId MUST assign random deviceId.", alice.getDeviceId(), bob.getDeviceId());
|
||||
|
||||
OmemoStore<?,?,?,?,?,?,?,?,?> store = OmemoService.getInstance().getOmemoStoreBackend();
|
||||
OmemoFingerprint finger = new OmemoFingerprint("FINGER");
|
||||
// DefaultDeviceId
|
||||
store.setDefaultDeviceId(alice.getOwnJid(), 777);
|
||||
assertEquals("defaultDeviceId setting/getting must equal.", 777, store.getDefaultDeviceId(alice.getOwnJid()));
|
||||
|
||||
// Trust/Distrust/Decide
|
||||
bob.shutdown();
|
||||
bob = OmemoManager.getInstanceFor(conTwo, 998);
|
||||
assertFalse("Bobs device MUST be undecided at this point",
|
||||
store.isDecidedOmemoIdentity(alice, bob.getOwnDevice(), finger));
|
||||
assertFalse("Bobs device MUST not be trusted at this point",
|
||||
store.isTrustedOmemoIdentity(alice, bob.getOwnDevice(), finger));
|
||||
store.trustOmemoIdentity(alice, bob.getOwnDevice(), finger);
|
||||
assertTrue("Bobs device MUST be trusted at this point.",
|
||||
store.isTrustedOmemoIdentity(alice, bob.getOwnDevice(), finger));
|
||||
assertTrue("Bobs device MUST be decided at this point.",
|
||||
store.isDecidedOmemoIdentity(alice, bob.getOwnDevice(), finger));
|
||||
store.distrustOmemoIdentity(alice, bob.getOwnDevice(), finger);
|
||||
assertFalse("Bobs device MUST be untrusted at this point.",
|
||||
store.isTrustedOmemoIdentity(alice, bob.getOwnDevice(), finger));
|
||||
|
||||
// Dates
|
||||
assertNull("Date of last received message must be null when no message was received ever.",
|
||||
store.getDateOfLastReceivedMessage(alice, bob.getOwnDevice()));
|
||||
Date now = new Date();
|
||||
store.setDateOfLastReceivedMessage(alice, bob.getOwnDevice(), now);
|
||||
assertEquals("Date of last received message must match the one we set.",
|
||||
now, store.getDateOfLastReceivedMessage(alice, bob.getOwnDevice()));
|
||||
assertNull("Date of last signed preKey renewal must be null.",
|
||||
store.getDateOfLastSignedPreKeyRenewal(alice));
|
||||
store.setDateOfLastSignedPreKeyRenewal(alice, now);
|
||||
assertEquals("Date of last signed preKey renewal must match our date.",
|
||||
now, store.getDateOfLastSignedPreKeyRenewal(alice));
|
||||
|
||||
// Keys
|
||||
assertNull("IdentityKeyPair must be null at this point.",
|
||||
store.loadOmemoIdentityKeyPair(alice));
|
||||
assertNull("IdentityKey of contact must be null at this point.",
|
||||
store.loadOmemoIdentityKey(alice, bob.getOwnDevice()));
|
||||
assertEquals("PreKeys list must be of length 0 at this point.",
|
||||
0, store.loadOmemoPreKeys(alice).size());
|
||||
assertEquals("SignedPreKeys list must be of length 0 at this point.",
|
||||
0, store.loadOmemoSignedPreKeys(alice).size());
|
||||
|
||||
assertNotNull("Generated IdentityKeyPair must not be null.",
|
||||
store.generateOmemoIdentityKeyPair());
|
||||
assertEquals("Generated PreKey list must be of correct length.",
|
||||
100, store.generateOmemoPreKeys(1, 100).size());
|
||||
|
||||
|
||||
// LastPreKeyId
|
||||
assertEquals("LastPreKeyId must be 0 at this point.",
|
||||
0, store.loadLastPreKeyId(alice));
|
||||
store.storeLastPreKeyId(alice, 1234);
|
||||
Thread.sleep(100);
|
||||
assertEquals("LastPreKeyId set/get must equal.", 1234, store.loadLastPreKeyId(alice));
|
||||
store.storeLastPreKeyId(alice, 0);
|
||||
|
||||
// CurrentSignedPreKeyId
|
||||
assertEquals("CurrentSignedPreKeyId must be 0 at this point.",
|
||||
0, store.loadCurrentSignedPreKeyId(alice));
|
||||
store.storeCurrentSignedPreKeyId(alice, 554);
|
||||
Thread.sleep(100);
|
||||
assertEquals("CurrentSignedPreKeyId must match the value we set.",
|
||||
554, store.loadCurrentSignedPreKeyId(alice));
|
||||
store.storeCurrentSignedPreKeyId(alice, 0);
|
||||
|
||||
deletePath(alice);
|
||||
|
||||
// ################# POST-INITIALIZATION #################
|
||||
setUpOmemoManager(alice);
|
||||
|
||||
// Keys
|
||||
assertNotNull("IdentityKeyPair must not be null after initialization",
|
||||
store.loadOmemoIdentityKeyPair(alice));
|
||||
assertNotSame("LastPreKeyId must not be 0 after initialization.",
|
||||
0, store.loadLastPreKeyId(alice));
|
||||
assertNotSame("currentSignedPreKeyId must not be 0 after initialization.",
|
||||
0, store.loadCurrentSignedPreKeyId(alice));
|
||||
assertNotNull("The last PreKey must not be null.",
|
||||
store.loadOmemoPreKey(alice, store.loadLastPreKeyId(alice) - 1));
|
||||
assertNotNull("The current signedPreKey must not be null.",
|
||||
store.loadOmemoSignedPreKey(alice, store.loadCurrentSignedPreKeyId(alice)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void after() {
|
||||
cleanServerSideTraces(alice);
|
||||
cleanServerSideTraces(bob);
|
||||
alice.shutdown();
|
||||
bob.shutdown();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017 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.SmackException;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
|
||||
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
|
||||
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
|
||||
import org.igniterealtime.smack.inttest.TestNotPossibleException;
|
||||
|
||||
public class SessionRenegotiationIntegrationTest extends AbstractTwoUsersOmemoIntegrationTest {
|
||||
|
||||
public SessionRenegotiationIntegrationTest(SmackIntegrationTestEnvironment environment)
|
||||
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
|
||||
SmackException.NoResponseException, TestNotPossibleException {
|
||||
super(environment);
|
||||
}
|
||||
|
||||
@SmackIntegrationTest
|
||||
public void sessionRenegotiationTest() throws Exception {
|
||||
|
||||
boolean prevRepairProperty = OmemoConfiguration.getRepairBrokenSessionsWithPreKeyMessages();
|
||||
OmemoConfiguration.setRepairBrokenSessionsWithPrekeyMessages(true);
|
||||
boolean prevCompleteSessionProperty = OmemoConfiguration.getCompleteSessionWithEmptyMessage();
|
||||
OmemoConfiguration.setCompleteSessionWithEmptyMessage(false);
|
||||
|
||||
// send PreKeyMessage -> Success
|
||||
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);
|
||||
bob.addOmemoMessageListener(listener1);
|
||||
alice.getConnection().sendStanza(e1.asMessage(bob.getOwnJid()));
|
||||
listener1.getSyncPoint().waitForResult(10 * 1000);
|
||||
bob.removeOmemoMessageListener(listener1);
|
||||
|
||||
// Remove the session on Bobs side.
|
||||
synchronized (bob.LOCK) {
|
||||
bob.getOmemoService().getOmemoStoreBackend().removeRawSession(bob.getOwnDevice(), alice.getOwnDevice());
|
||||
}
|
||||
|
||||
// Send normal message -> fail, bob repairs session with preKeyMessage
|
||||
final String body2 = "P = NP is also true for all N,P from the set of complex numbers, where N is equal to 1.";
|
||||
AbstractOmemoMessageListener.PreKeyKeyTransportListener listener2 =
|
||||
new AbstractOmemoMessageListener.PreKeyKeyTransportListener();
|
||||
OmemoMessage.Sent e2 = alice.encrypt(bob.getOwnJid(), body2);
|
||||
alice.addOmemoMessageListener(listener2);
|
||||
alice.getConnection().sendStanza(e2.asMessage(bob.getOwnJid()));
|
||||
listener2.getSyncPoint().waitForResult(10 * 1000);
|
||||
alice.removeOmemoMessageListener(listener2);
|
||||
|
||||
// Send normal message -> success
|
||||
final String body3 = "P = NP would be a disaster for the world of cryptography.";
|
||||
AbstractOmemoMessageListener.MessageListener listener3 = new AbstractOmemoMessageListener.MessageListener(body3);
|
||||
OmemoMessage.Sent e3 = alice.encrypt(bob.getOwnJid(), body3);
|
||||
bob.addOmemoMessageListener(listener3);
|
||||
alice.getConnection().sendStanza(e3.asMessage(bob.getOwnJid()));
|
||||
listener3.getSyncPoint().waitForResult(10 * 1000);
|
||||
bob.removeOmemoMessageListener(listener3);
|
||||
|
||||
OmemoConfiguration.setRepairBrokenSessionsWithPrekeyMessages(prevRepairProperty);
|
||||
OmemoConfiguration.setCompleteSessionWithEmptyMessage(prevCompleteSessionProperty);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue