1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-10-31 17:25:58 +01:00

Move trust management from OmemoStore to TrustCallback

Refactor various method names to more precisely reflect
what happens

Introduce CachingOmemoStore, SignalCachingOmemoStore,
which can be either used standalone as ephemeral OmemoStore
implementations, or as wrappers around other implementations
to add a cache layer for reduced storage access.

Get rid of "isFreshInstallation". Keys are now - given
that they don't exist - generated on startup.

Bump libsignal-protocol-java to 2.6.2

Prevent offline access to some functions which require
authenticated connection

Create more advanced unit tests and integration tests

Add async initialization function for OmemoManager

Remove session handling from smack-omemo.
This is now handled - in case of smack-omemo-signal -
solely by libsignal-protocol-java
This commit is contained in:
vanitasvitae 2017-11-28 15:33:41 +01:00 committed by Paul Schaub
parent fb7a22a761
commit 36dae1ece4
53 changed files with 4072 additions and 3408 deletions

View file

@ -548,10 +548,12 @@ task clirrRootReport(type: org.kordamp.gradle.clirr.ClirrReportTask) {
}
task integrationTest {
description 'Verify correct functionality of Smack by running some integration tests.'
dependsOn project(':smack-integration-test').tasks.run
}
task omemoSignalIntTest {
description 'Run integration tests of the smack-omemo module in combination with smack-omemo-signal.'
dependsOn project(':smack-omemo-signal-integration-test').tasks.run
}

View file

@ -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

View file

@ -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)");
@ -60,22 +41,4 @@ public abstract class AbstractOmemoIntegrationTest extends AbstractSmackIntegrat
throw new TestNotPossibleException("No OmemoService registered.");
}
}
@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();
}

View file

@ -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() throws SmackException.NotLoggedInException {
alice.stopListeners();
bob.stopListeners();
OmemoManagerSetupHelper.cleanUpPubSub(alice);
OmemoManagerSetupHelper.cleanUpRoster(alice);
OmemoManagerSetupHelper.cleanUpPubSub(bob);
OmemoManagerSetupHelper.cleanUpRoster(bob);
}
}

View file

@ -0,0 +1,116 @@
/**
*
* 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 org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smackx.omemo.element.OmemoBundleElement;
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;
/**
* 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);
}
@SmackIntegrationTest
public void messageTest() throws Exception {
OmemoBundleElement aliceBundle1 = alice.getOmemoService().getOmemoStoreBackend().packOmemoBundle(alice.getOwnDevice());
OmemoBundleElement bobsBundle1 = bob.getOmemoService().getOmemoStoreBackend().packOmemoBundle(bob.getOwnDevice());
final String message1 = "One is greater than zero (for small values of zero).";
Message encrypted1 = alice.encrypt(bob.getOwnJid(), message1);
final SimpleResultSyncPoint bobReceivedMessage = new SimpleResultSyncPoint();
bob.addOmemoMessageListener(new OmemoMessageListener() {
@Override
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
if (decryptedBody.equals(message1)) {
bobReceivedMessage.signal();
} else {
bobReceivedMessage.signalFailure("Received decrypted message was not equal to sent message.");
}
}
@Override
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
// Not used
}
});
encrypted1.setTo(bob.getOwnJid());
alice.getConnection().sendStanza(encrypted1);
bobReceivedMessage.waitForResult(10 * 1000);
OmemoBundleElement aliceBundle2 = alice.getOmemoService().getOmemoStoreBackend().packOmemoBundle(alice.getOwnDevice());
OmemoBundleElement bobsBundle2 = 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 Message encrypted2 = bob.encrypt(alice.getOwnJid(), message2);
final SimpleResultSyncPoint aliceReceivedMessage = new SimpleResultSyncPoint();
alice.addOmemoMessageListener(new OmemoMessageListener() {
@Override
public void onOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
if (decryptedBody.equals(message2)) {
aliceReceivedMessage.signal();
} else {
aliceReceivedMessage.signalFailure("Received decrypted message was not equal to sent message.");
}
}
@Override
public void onOmemoKeyTransportReceived(CipherAndAuthTag cipherAndAuthTag, Message message, Message wrappingMessage, OmemoMessageInformation omemoInformation) {
// Not needed here either.
}
});
encrypted2.setTo(alice.getOwnJid());
bob.getConnection().sendStanza(encrypted2);
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);
}
}

View file

@ -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);
}
}

View file

@ -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));
}
}

View file

@ -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);
}
}

View file

@ -0,0 +1,239 @@
/**
*
* 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.omemo.util.EphemeralTrustCallback;
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.CachedDeviceList;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
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
{
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
{
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) throws SmackException.NotLoggedInException {
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.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
}
}
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -10,7 +10,8 @@ dependencies {
compile project(":smack-im")
compile project(":smack-extensions")
compile project(":smack-omemo")
compile 'org.whispersystems:signal-protocol-java:2.4.0'
compile 'org.whispersystems:signal-protocol-java:2.6.2'
testCompile project(path: ":smack-core", configuration: "testRuntime")
testCompile project(path: ":smack-omemo", configuration: "testRuntime")
}

View file

@ -0,0 +1,62 @@
/**
*
* Copyright 2017 Paul Schaub
*
* This file is part of smack-omemo-signal.
*
* smack-omemo-signal is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jivesoftware.smackx.omemo.signal;
import org.jivesoftware.smackx.omemo.CachingOmemoStore;
import org.jivesoftware.smackx.omemo.OmemoStore;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.SessionCipher;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.state.PreKeyBundle;
import org.whispersystems.libsignal.state.PreKeyRecord;
import org.whispersystems.libsignal.state.SessionRecord;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
/**
* Implementation of the CachingOmemoStore for smack-omemo-signal.
* This Store implementation can either be used as a proxy wrapping a persistent SignalOmemoStore in order to prevent
* excessive storage access, or it can be used standalone as an ephemeral store, which doesn't persist its contents.
*/
public class SignalCachingOmemoStore extends CachingOmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord,
SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher>
{
/**
* Create a new SignalCachingOmemoStore as a caching layer around a persisting OmemoStore
* (eg. a SignalFileBasedOmemoStore).
* @param wrappedStore
*/
public SignalCachingOmemoStore(OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord,
SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> wrappedStore)
{
super(wrappedStore);
}
/**
* Create a new SignalCachingOmemoStore as an ephemeral standalone OmemoStore.
*/
public SignalCachingOmemoStore() {
super(new SignalOmemoKeyUtil());
}
}

View file

@ -42,7 +42,9 @@ import org.whispersystems.libsignal.state.SignedPreKeyRecord;
*/
@SuppressWarnings("unused")
public class SignalFileBasedOmemoStore
extends FileBasedOmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> {
extends FileBasedOmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord,
SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher>
{
public SignalFileBasedOmemoStore() {
super();
@ -53,7 +55,9 @@ public class SignalFileBasedOmemoStore
}
@Override
public OmemoKeyUtil<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> keyUtil() {
public OmemoKeyUtil<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord,
SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> keyUtil()
{
return new SignalOmemoKeyUtil();
}
}

View file

@ -21,16 +21,13 @@
package org.jivesoftware.smackx.omemo.signal;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.TreeMap;
import org.jivesoftware.smackx.omemo.OmemoFingerprint;
import org.jivesoftware.smackx.omemo.OmemoManager;
import org.jivesoftware.smackx.omemo.OmemoStore;
import org.jivesoftware.smackx.omemo.element.OmemoBundleVAxolotlElement;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.internal.OmemoSession;
import org.jivesoftware.smackx.omemo.util.OmemoKeyUtil;
import org.jxmpp.jid.impl.JidCreate;
@ -54,25 +51,27 @@ import org.whispersystems.libsignal.util.KeyHelper;
* @author Paul Schaub
*/
public class SignalOmemoKeyUtil extends OmemoKeyUtil<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord,
SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> {
SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher>
{
@Override
public IdentityKeyPair generateOmemoIdentityKeyPair() {
return KeyHelper.generateIdentityKeyPair();
}
@Override
public HashMap<Integer, PreKeyRecord> generateOmemoPreKeys(int currentPreKeyId, int count) {
public TreeMap<Integer, PreKeyRecord> generateOmemoPreKeys(int currentPreKeyId, int count) {
List<PreKeyRecord> preKeyRecords = KeyHelper.generatePreKeys(currentPreKeyId, count);
HashMap<Integer, PreKeyRecord> hashMap = new HashMap<>();
TreeMap<Integer, PreKeyRecord> map = new TreeMap<>();
for (PreKeyRecord p : preKeyRecords) {
hashMap.put(p.getId(), p);
map.put(p.getId(), p);
}
return hashMap;
return map;
}
@Override
public SignedPreKeyRecord generateOmemoSignedPreKey(IdentityKeyPair identityKeyPair, int currentPreKeyId) throws CorruptedOmemoKeyException {
public SignedPreKeyRecord generateOmemoSignedPreKey(IdentityKeyPair identityKeyPair, int currentPreKeyId)
throws CorruptedOmemoKeyException
{
try {
return KeyHelper.generateSignedPreKey(identityKeyPair, currentPreKeyId);
} catch (InvalidKeyException e) {
@ -82,6 +81,7 @@ public class SignalOmemoKeyUtil extends OmemoKeyUtil<IdentityKeyPair, IdentityKe
@Override
public SignedPreKeyRecord signedPreKeyFromBytes(byte[] data) throws IOException {
if (data == null) return null;
return new SignedPreKeyRecord(data);
}
@ -90,21 +90,9 @@ public class SignalOmemoKeyUtil extends OmemoKeyUtil<IdentityKeyPair, IdentityKe
return signedPreKeyRecord.serialize();
}
@Override
public OmemoSession<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher>
createOmemoSession(OmemoManager omemoManager, OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> omemoStore,
OmemoDevice contact, IdentityKey identityKey) {
return new SignalOmemoSession(omemoManager, omemoStore, contact, identityKey);
}
@Override
public OmemoSession<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher>
createOmemoSession(OmemoManager omemoManager, OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> omemoStore, OmemoDevice from) {
return new SignalOmemoSession(omemoManager, omemoStore, from);
}
@Override
public SessionRecord rawSessionFromBytes(byte[] data) throws IOException {
if (data == null) return null;
return new SessionRecord(data);
}
@ -115,6 +103,7 @@ public class SignalOmemoKeyUtil extends OmemoKeyUtil<IdentityKeyPair, IdentityKe
@Override
public IdentityKeyPair identityKeyPairFromBytes(byte[] data) throws CorruptedOmemoKeyException {
if (data == null) return null;
try {
return new IdentityKeyPair(data);
} catch (InvalidKeyException e) {
@ -124,6 +113,7 @@ public class SignalOmemoKeyUtil extends OmemoKeyUtil<IdentityKeyPair, IdentityKe
@Override
public IdentityKey identityKeyFromBytes(byte[] data) throws CorruptedOmemoKeyException {
if (data == null) return null;
try {
return new IdentityKey(data, 0);
} catch (InvalidKeyException e) {
@ -133,6 +123,7 @@ public class SignalOmemoKeyUtil extends OmemoKeyUtil<IdentityKeyPair, IdentityKe
@Override
public ECPublicKey ellipticCurvePublicKeyFromBytes(byte[] data) throws CorruptedOmemoKeyException {
if (data == null) return null;
try {
return Curve.decodePoint(data, 0);
} catch (InvalidKeyException e) {
@ -147,11 +138,14 @@ public class SignalOmemoKeyUtil extends OmemoKeyUtil<IdentityKeyPair, IdentityKe
@Override
public PreKeyRecord preKeyFromBytes(byte[] bytes) throws IOException {
if (bytes == null) return null;
return new PreKeyRecord(bytes);
}
@Override
public PreKeyBundle bundleFromOmemoBundle(OmemoBundleVAxolotlElement bundle, OmemoDevice contact, int preKeyId) throws CorruptedOmemoKeyException {
public PreKeyBundle bundleFromOmemoBundle(OmemoBundleVAxolotlElement bundle, OmemoDevice contact, int preKeyId)
throws CorruptedOmemoKeyException
{
return new PreKeyBundle(0,
contact.getDeviceId(),
preKeyId,
@ -208,20 +202,35 @@ public class SignalOmemoKeyUtil extends OmemoKeyUtil<IdentityKeyPair, IdentityKe
}
@Override
public OmemoFingerprint getFingerprint(IdentityKey identityKey) {
public OmemoFingerprint getFingerprintOfIdentityKey(IdentityKey identityKey) {
if (identityKey == null) {
return null;
}
String fp = identityKey.getFingerprint();
// Cut "(byte)0x" prefixes, remove spaces and commas, cut first two digits.
fp = fp.replace("(byte)0x", "").replace(",", "").replace(" ", "").substring(2);
fp = fp.replace("(byte)0x", "").replace(",", "")
.replace(" ", "").substring(2);
return new OmemoFingerprint(fp);
}
@Override
public OmemoFingerprint getFingerprintOfIdentityKeyPair(IdentityKeyPair identityKeyPair) {
if (identityKeyPair == null) {
return null;
}
return getFingerprintOfIdentityKey(identityKeyPair.getPublicKey());
}
@Override
public SignalProtocolAddress omemoDeviceAsAddress(OmemoDevice contact) {
return new SignalProtocolAddress(contact.getJid().asBareJid().toString(), contact.getDeviceId());
}
@Override
public OmemoDevice addressAsOmemoDevice(SignalProtocolAddress address) throws XmppStringprepException {
public OmemoDevice addressAsOmemoDevice(SignalProtocolAddress address)
throws XmppStringprepException
{
return new OmemoDevice(JidCreate.bareFrom(address.getName()), address.getDeviceId());
}
}

View file

@ -26,7 +26,6 @@ import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.logging.Level;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
@ -34,7 +33,6 @@ import javax.crypto.NoSuchPaddingException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smackx.omemo.OmemoManager;
import org.jivesoftware.smackx.omemo.OmemoService;
import org.jivesoftware.smackx.omemo.OmemoStore;
@ -59,16 +57,31 @@ import org.whispersystems.libsignal.state.SignedPreKeyRecord;
* @author Paul Schaub
*/
@SuppressWarnings("unused")
public final class SignalOmemoService extends OmemoService<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> {
public final class SignalOmemoService
extends OmemoService<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord,
SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher>
{
private static SignalOmemoService INSTANCE;
private static boolean LICENSE_ACKNOWLEDGED = false;
public static void setup() throws InvalidKeyException, XMPPErrorException, NoSuchPaddingException, InvalidAlgorithmParameterException, UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchProviderException, SmackException, InterruptedException, CorruptedOmemoKeyException {
@Override
protected SignalOmemoSessionManager createOmemoSessionManager(
OmemoManager.KnownBareJidGuard manager,
OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord,
SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> store)
{
return new SignalOmemoSessionManager(manager, getOmemoStoreBackend());
}
public static void setup()
throws InvalidKeyException, XMPPErrorException, NoSuchPaddingException, InvalidAlgorithmParameterException,
UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException,
NoSuchProviderException, SmackException, InterruptedException, CorruptedOmemoKeyException
{
if (!LICENSE_ACKNOWLEDGED) {
throw new IllegalStateException("smack-omemo-signal is licensed under the terms of the GPLv3. Please be aware that you " +
"can only use this library within the terms of the GPLv3. See for example " +
"https://www.gnu.org/licenses/quick-guide-gplv3 for more details. Please call " +
throw new IllegalStateException("smack-omemo-signal is licensed under the terms of the GPLv3. " +
"Please be aware that you can only use this library within the terms of the GPLv3. " +
"See for example https://www.gnu.org/licenses/quick-guide-gplv3 for more details. Please call " +
"SignalOmemoService.acknowledgeLicense() prior to the setup() method in order to prevent " +
"this exception.");
}
@ -79,8 +92,10 @@ public final class SignalOmemoService extends OmemoService<IdentityKeyPair, Iden
}
@Override
public OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> createDefaultOmemoStoreBackend() {
return new SignalFileBasedOmemoStore();
public OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord,
SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher>
createDefaultOmemoStoreBackend() {
return new SignalCachingOmemoStore();
}
private SignalOmemoService()
@ -96,14 +111,17 @@ public final class SignalOmemoService extends OmemoService<IdentityKeyPair, Iden
}
@Override
protected void processBundle(OmemoManager omemoManager, PreKeyBundle preKeyBundle, OmemoDevice contact) throws CorruptedOmemoKeyException {
SignalOmemoStoreConnector connector = new SignalOmemoStoreConnector(omemoManager, getOmemoStoreBackend());
protected void processBundle(OmemoManager.KnownBareJidGuard managerGuard,
PreKeyBundle preKeyBundle,
OmemoDevice device)
throws CorruptedOmemoKeyException
{
SignalOmemoStoreConnector connector = new SignalOmemoStoreConnector(managerGuard, getOmemoStoreBackend());
SessionBuilder builder = new SessionBuilder(connector, connector, connector, connector,
getOmemoStoreBackend().keyUtil().omemoDeviceAsAddress(contact));
getOmemoStoreBackend().keyUtil().omemoDeviceAsAddress(device));
try {
builder.process(preKeyBundle);
LOGGER.log(Level.INFO, "Session built with " + contact);
getOmemoStoreBackend().getOmemoSessionOf(omemoManager, contact); //method puts session in session map.
LOGGER.log(Level.FINE, "Session built with " + device);
} catch (org.whispersystems.libsignal.InvalidKeyException e) {
throw new CorruptedOmemoKeyException(e.getMessage());
} catch (UntrustedIdentityException e) {

View file

@ -1,142 +0,0 @@
/**
*
* Copyright 2017 Paul Schaub
*
* This file is part of smack-omemo-signal.
*
* smack-omemo-signal is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jivesoftware.smackx.omemo.signal;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smackx.omemo.OmemoManager;
import org.jivesoftware.smackx.omemo.OmemoStore;
import org.jivesoftware.smackx.omemo.element.OmemoElement;
import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException;
import org.jivesoftware.smackx.omemo.internal.CiphertextTuple;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.internal.OmemoSession;
import org.whispersystems.libsignal.DuplicateMessageException;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.InvalidKeyIdException;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.InvalidVersionException;
import org.whispersystems.libsignal.LegacyMessageException;
import org.whispersystems.libsignal.NoSessionException;
import org.whispersystems.libsignal.SessionCipher;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.UntrustedIdentityException;
import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.protocol.CiphertextMessage;
import org.whispersystems.libsignal.protocol.PreKeySignalMessage;
import org.whispersystems.libsignal.protocol.SignalMessage;
import org.whispersystems.libsignal.state.PreKeyBundle;
import org.whispersystems.libsignal.state.PreKeyRecord;
import org.whispersystems.libsignal.state.SessionRecord;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
/**
* Concrete implementation of the OmemoSession using the Signal library.
*
* @author Paul Schaub
*/
public class SignalOmemoSession extends OmemoSession<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> {
private static final Logger LOGGER = Logger.getLogger(SignalOmemoSession.class.getName());
/**
* Constructor used when the remote user initialized the session using a PreKeyOmemoMessage.
*
* @param omemoManager omemoManager
* @param omemoStore omemoStoreConnector that can be used to get information from
* @param remoteContact omemoDevice of the remote contact
* @param identityKey identityKey of the remote contact
*/
SignalOmemoSession(OmemoManager omemoManager, OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> omemoStore,
OmemoDevice remoteContact, IdentityKey identityKey) {
super(omemoManager, omemoStore, remoteContact, identityKey);
}
/**
* Constructor used when we initiate a new Session with the remote user.
*
* @param omemoManager omemoManager
* @param omemoStore omemoStore used to get information from
* @param remoteContact omemoDevice of the remote contact
*/
SignalOmemoSession(OmemoManager omemoManager,
OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> omemoStore,
OmemoDevice remoteContact) {
super(omemoManager, omemoStore, remoteContact);
}
@Override
public SessionCipher createCipher(OmemoDevice contact) {
SignalOmemoStoreConnector connector = new SignalOmemoStoreConnector(omemoManager, omemoStore);
return new SessionCipher(connector, connector, connector, connector,
omemoStore.keyUtil().omemoDeviceAsAddress(contact));
}
@Override
public CiphertextTuple encryptMessageKey(byte[] messageKey) {
CiphertextMessage ciphertextMessage;
ciphertextMessage = cipher.encrypt(messageKey);
int type = (ciphertextMessage.getType() == CiphertextMessage.PREKEY_TYPE ?
OmemoElement.TYPE_OMEMO_PREKEY_MESSAGE : OmemoElement.TYPE_OMEMO_MESSAGE);
return new CiphertextTuple(ciphertextMessage.serialize(), type);
}
@Override
public byte[] decryptMessageKey(byte[] encryptedKey) throws NoRawSessionException {
byte[] decryptedKey = null;
try {
try {
PreKeySignalMessage message = new PreKeySignalMessage(encryptedKey);
if (!message.getPreKeyId().isPresent()) {
LOGGER.log(Level.WARNING, "PreKeySignalMessage did not contain a PreKeyId");
return null;
}
LOGGER.log(Level.INFO, "PreKeySignalMessage received, new session ID: " + message.getSignedPreKeyId() + "/" + message.getPreKeyId().get());
IdentityKey messageIdentityKey = message.getIdentityKey();
if (this.identityKey != null && !this.identityKey.equals(messageIdentityKey)) {
LOGGER.log(Level.INFO, "Had session with fingerprint " + getFingerprint() +
", received message with different fingerprint " + omemoStore.keyUtil().getFingerprint(messageIdentityKey) +
". Silently drop the message.");
} else {
this.identityKey = messageIdentityKey;
decryptedKey = cipher.decrypt(message);
this.preKeyId = message.getPreKeyId().get();
}
} catch (InvalidMessageException | InvalidVersionException e) {
SignalMessage message = new SignalMessage(encryptedKey);
decryptedKey = cipher.decrypt(message);
} catch (InvalidKeyIdException e) {
throw new NoRawSessionException(e);
}
catch (InvalidKeyException | UntrustedIdentityException e) {
LOGGER.log(Level.SEVERE, "Error decrypting message header, " + e.getClass().getName() + ": " + e.getMessage());
}
} catch (InvalidMessageException | NoSessionException e) {
throw new NoRawSessionException(e);
} catch (LegacyMessageException | DuplicateMessageException e) {
LOGGER.log(Level.SEVERE, "Error decrypting message header, " + e.getClass().getName() + ": " + e.getMessage());
}
return decryptedKey;
}
}

View file

@ -0,0 +1,164 @@
/**
*
* Copyright 2017 Paul Schaub
*
* This file is part of smack-omemo-signal.
*
* smack-omemo-signal is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jivesoftware.smackx.omemo.signal;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smackx.omemo.OmemoManager;
import org.jivesoftware.smackx.omemo.OmemoSessionManager;
import org.jivesoftware.smackx.omemo.OmemoStore;
import org.jivesoftware.smackx.omemo.element.OmemoElement;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException;
import org.jivesoftware.smackx.omemo.exceptions.UntrustedOmemoIdentityException;
import org.jivesoftware.smackx.omemo.internal.CiphertextTuple;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.whispersystems.libsignal.DuplicateMessageException;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.InvalidKeyIdException;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.InvalidVersionException;
import org.whispersystems.libsignal.LegacyMessageException;
import org.whispersystems.libsignal.NoSessionException;
import org.whispersystems.libsignal.SessionCipher;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.UntrustedIdentityException;
import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.protocol.CiphertextMessage;
import org.whispersystems.libsignal.protocol.PreKeySignalMessage;
import org.whispersystems.libsignal.protocol.SignalMessage;
import org.whispersystems.libsignal.state.PreKeyBundle;
import org.whispersystems.libsignal.state.PreKeyRecord;
import org.whispersystems.libsignal.state.SessionRecord;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
public class SignalOmemoSessionManager
extends OmemoSessionManager<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord,
SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher>
{
private static final Logger LOGGER = Logger.getLogger(OmemoSessionManager.class.getName());
private final SignalOmemoStoreConnector storeConnector;
public SignalOmemoSessionManager(OmemoManager.KnownBareJidGuard managerGuard,
OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord,
SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle,
SessionCipher> store)
{
super(managerGuard, store);
this.storeConnector = new SignalOmemoStoreConnector(managerGuard, store);
}
@Override
public byte[] doubleRatchetDecrypt(OmemoDevice sender, byte[] encryptedKey)
throws CorruptedOmemoKeyException, NoRawSessionException, CryptoFailedException,
UntrustedOmemoIdentityException
{
SessionCipher cipher = getCipher(sender);
byte[] decryptedKey;
// Try to handle the message as a PreKeySignalMessage...
try {
PreKeySignalMessage preKeyMessage = new PreKeySignalMessage(encryptedKey);
if (!preKeyMessage.getPreKeyId().isPresent()) {
throw new CryptoFailedException("PreKeyMessage did not contain a preKeyId.");
}
IdentityKey messageIdentityKey = preKeyMessage.getIdentityKey();
IdentityKey previousIdentityKey = store.loadOmemoIdentityKey(storeConnector.getOurDevice(), sender);
if (previousIdentityKey != null &&
!previousIdentityKey.getFingerprint().equals(messageIdentityKey.getFingerprint())) {
throw new UntrustedOmemoIdentityException(sender,
store.keyUtil().getFingerprintOfIdentityKey(previousIdentityKey),
store.keyUtil().getFingerprintOfIdentityKey(messageIdentityKey));
}
try {
decryptedKey = cipher.decrypt(preKeyMessage);
}
catch (UntrustedIdentityException e) {
throw new AssertionError("Signals trust management MUST be disabled.");
}
catch (LegacyMessageException | InvalidKeyException e) {
throw new CryptoFailedException(e);
}
catch (InvalidKeyIdException e) {
throw new NoRawSessionException(e);
}
catch (DuplicateMessageException e) {
LOGGER.log(Level.INFO, "Decryption of PreKeyMessage from " + sender +
" failed, since the message has been decrypted before.");
return null;
}
} catch (InvalidVersionException | InvalidMessageException noPreKeyMessage) {
// ...if that fails, handle it as a SignalMessage
try {
SignalMessage message = new SignalMessage(encryptedKey);
decryptedKey = getCipher(sender).decrypt(message);
}
catch (UntrustedIdentityException e) {
throw new AssertionError("Signals trust management MUST be disabled.");
}
catch (InvalidMessageException | NoSessionException e) {
throw new NoRawSessionException(e);
}
catch (LegacyMessageException e) {
throw new CryptoFailedException(e);
}
catch (DuplicateMessageException e1) {
LOGGER.log(Level.INFO, "Decryption of SignalMessage from " + sender +
" failed, since the message has been decrypted before.");
return null;
}
}
return decryptedKey;
}
@Override
public CiphertextTuple doubleRatchetEncrypt(OmemoDevice recipient, byte[] messageKey) {
CiphertextMessage ciphertextMessage;
try {
ciphertextMessage = getCipher(recipient).encrypt(messageKey);
} catch (UntrustedIdentityException e) {
throw new AssertionError("Signals trust management MUST be disabled.");
}
// TODO: Figure out, if this is enough...
int type = (ciphertextMessage.getType() == CiphertextMessage.PREKEY_TYPE ?
OmemoElement.TYPE_OMEMO_PREKEY_MESSAGE : OmemoElement.TYPE_OMEMO_MESSAGE);
return new CiphertextTuple(ciphertextMessage.serialize(), type);
}
private SessionCipher getCipher(OmemoDevice device) {
SignalOmemoKeyUtil keyUtil = (SignalOmemoKeyUtil) store.keyUtil();
SignalProtocolAddress address = keyUtil.omemoDeviceAsAddress(device);
return new SessionCipher(storeConnector, storeConnector, storeConnector, storeConnector, address);
}
}

View file

@ -40,12 +40,14 @@ import org.whispersystems.libsignal.state.SignedPreKeyRecord;
*/
@SuppressWarnings("unused")
public abstract class SignalOmemoStore
extends OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> {
extends OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord,
SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> {
private final SignalOmemoKeyUtil signalKeyUtil = new SignalOmemoKeyUtil();
@Override
public OmemoKeyUtil<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> keyUtil() {
public OmemoKeyUtil<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord,
SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> keyUtil() {
return signalKeyUtil;
}
}

View file

@ -21,15 +21,17 @@
package org.jivesoftware.smackx.omemo.signal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smackx.omemo.OmemoManager;
import org.jivesoftware.smackx.omemo.OmemoStore;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
import org.whispersystems.libsignal.IdentityKey;
@ -57,21 +59,28 @@ public class SignalOmemoStoreConnector
private static final Logger LOGGER = Logger.getLogger(SignalOmemoStoreConnector.class.getName());
private final OmemoManager omemoManager;
private final OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher>
private final OmemoManager.KnownBareJidGuard managerGuard;
private final OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord,
SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher>
omemoStore;
public SignalOmemoStoreConnector(OmemoManager omemoManager, OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> store) {
this.omemoManager = omemoManager;
public SignalOmemoStoreConnector(OmemoManager.KnownBareJidGuard managerGuard, OmemoStore<IdentityKeyPair,
IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey,
PreKeyBundle, SessionCipher> store) {
this.managerGuard = managerGuard;
this.omemoStore = store;
}
OmemoDevice getOurDevice() {
return managerGuard.get().getOwnDevice();
}
@Override
public IdentityKeyPair getIdentityKeyPair() {
try {
return omemoStore.loadOmemoIdentityKeyPair(omemoManager);
return omemoStore.loadOmemoIdentityKeyPair(getOurDevice());
} catch (CorruptedOmemoKeyException e) {
LOGGER.log(Level.SEVERE, "getIdentityKeyPair has failed: " + e, e);
LOGGER.log(Level.SEVERE, "IdentityKeyPair seems to be invalid.", e);
return null;
}
}
@ -86,33 +95,41 @@ public class SignalOmemoStoreConnector
}
@Override
public void saveIdentity(SignalProtocolAddress signalProtocolAddress, IdentityKey identityKey) {
public boolean saveIdentity(SignalProtocolAddress signalProtocolAddress, IdentityKey identityKey) {
OmemoDevice device;
try {
omemoStore.storeOmemoIdentityKey(omemoManager, omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress), identityKey);
device = omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress);
} catch (XmppStringprepException e) {
throw new AssertionError(e);
}
omemoStore.storeOmemoIdentityKey(getOurDevice(), device, identityKey);
return true;
}
@Override
public boolean isTrustedIdentity(SignalProtocolAddress signalProtocolAddress, IdentityKey identityKey) {
// Disable internal trust management. Instead we use OmemoStore.isTrustedOmemoIdentity() before encrypting for a
// recipient.
public boolean isTrustedIdentity(SignalProtocolAddress signalProtocolAddress,
IdentityKey identityKey,
Direction direction) {
// Disable internal trust management. Instead we use OmemoStore.isTrustedOmemoIdentity() before encrypting
// for a recipient.
return true;
}
@Override
public PreKeyRecord loadPreKey(int i) throws InvalidKeyIdException {
PreKeyRecord pr = omemoStore.loadOmemoPreKey(omemoManager, i);
if (pr == null) {
throw new InvalidKeyIdException("No PreKey with Id " + i + " found!");
PreKeyRecord preKey = omemoStore.loadOmemoPreKey(getOurDevice(), i);
if (preKey == null) {
throw new InvalidKeyIdException("No PreKey with Id " + i + " found.");
}
return pr;
return preKey;
}
@Override
public void storePreKey(int i, PreKeyRecord preKeyRecord) {
omemoStore.storeOmemoPreKey(omemoManager, i, preKeyRecord);
omemoStore.storeOmemoPreKey(getOurDevice(), i, preKeyRecord);
}
@Override
@ -120,96 +137,112 @@ public class SignalOmemoStoreConnector
try {
return (loadPreKey(i) != null);
} catch (InvalidKeyIdException e) {
LOGGER.log(Level.WARNING, "containsPreKey has failed: " + e.getMessage());
return false;
}
}
@Override
public void removePreKey(int i) {
omemoStore.removeOmemoPreKey(omemoManager, i);
omemoStore.removeOmemoPreKey(getOurDevice(), i);
}
@Override
public SessionRecord loadSession(SignalProtocolAddress signalProtocolAddress) {
OmemoDevice device;
try {
SessionRecord s = omemoStore.loadRawSession(omemoManager, omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress));
return (s != null ? s : new SessionRecord());
device = omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress);
} catch (XmppStringprepException e) {
throw new AssertionError(e);
}
SessionRecord record = omemoStore.loadRawSession(getOurDevice(), device);
if (record != null) {
return record;
} else {
return new SessionRecord();
}
}
@Override
public List<Integer> getSubDeviceSessions(String s) {
HashMap<Integer, SessionRecord> contactsSessions;
BareJid jid;
try {
contactsSessions = omemoStore.loadAllRawSessionsOf(omemoManager, JidCreate.bareFrom(s));
jid = JidCreate.bareFrom(s);
} catch (XmppStringprepException e) {
throw new AssertionError(e);
}
if (contactsSessions != null) {
return new ArrayList<>(contactsSessions.keySet());
}
return new ArrayList<>();
return new ArrayList<>(omemoStore.loadAllRawSessionsOf(getOurDevice(), jid).keySet());
}
@Override
public void storeSession(SignalProtocolAddress signalProtocolAddress, SessionRecord sessionRecord) {
OmemoDevice device;
try {
omemoStore.storeRawSession(omemoManager, omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress), sessionRecord);
device = omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress);
} catch (XmppStringprepException e) {
throw new AssertionError(e);
}
omemoStore.storeRawSession(getOurDevice(), device, sessionRecord);
}
@Override
public boolean containsSession(SignalProtocolAddress signalProtocolAddress) {
OmemoDevice device;
try {
return omemoStore.containsRawSession(omemoManager, omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress));
device = omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress);
} catch (XmppStringprepException e) {
throw new AssertionError(e);
}
return omemoStore.containsRawSession(getOurDevice(), device);
}
@Override
public void deleteSession(SignalProtocolAddress signalProtocolAddress) {
OmemoDevice device;
try {
omemoStore.removeRawSession(omemoManager, omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress));
device = omemoStore.keyUtil().addressAsOmemoDevice(signalProtocolAddress);
} catch (XmppStringprepException e) {
throw new AssertionError(e);
}
omemoStore.removeRawSession(getOurDevice(), device);
}
@Override
public void deleteAllSessions(String s) {
BareJid jid;
try {
omemoStore.removeAllRawSessionsOf(omemoManager, JidCreate.bareFrom(s));
jid = JidCreate.bareFrom(s);
} catch (XmppStringprepException e) {
throw new AssertionError(e);
}
omemoStore.removeAllRawSessionsOf(getOurDevice(), jid);
}
@Override
public SignedPreKeyRecord loadSignedPreKey(int i) throws InvalidKeyIdException {
SignedPreKeyRecord spkr = omemoStore.loadOmemoSignedPreKey(omemoManager, i);
if (spkr == null) {
throw new InvalidKeyIdException("No SignedPreKey with Id " + i + " found!");
}
return spkr;
return omemoStore.loadOmemoSignedPreKey(getOurDevice(), i);
}
@Override
public List<SignedPreKeyRecord> loadSignedPreKeys() {
HashMap<Integer, SignedPreKeyRecord> signedPreKeyRecordHashMap = omemoStore.loadOmemoSignedPreKeys(omemoManager);
List<SignedPreKeyRecord> signedPreKeyRecordList = new ArrayList<>();
TreeMap<Integer, SignedPreKeyRecord> signedPreKeyRecordHashMap =
omemoStore.loadOmemoSignedPreKeys(getOurDevice());
signedPreKeyRecordList.addAll(signedPreKeyRecordHashMap.values());
return signedPreKeyRecordList;
}
@Override
public void storeSignedPreKey(int i, SignedPreKeyRecord signedPreKeyRecord) {
omemoStore.storeOmemoSignedPreKey(omemoManager, i, signedPreKeyRecord);
omemoStore.storeOmemoSignedPreKey(getOurDevice(), i, signedPreKeyRecord);
}
@Override
@ -224,6 +257,6 @@ public class SignalOmemoStoreConnector
@Override
public void removeSignedPreKey(int i) {
omemoStore.removeOmemoSignedPreKey(omemoManager, i);
omemoStore.removeOmemoSignedPreKey(getOurDevice(), i);
}
}

View file

@ -0,0 +1,122 @@
/**
*
* Copyright 2017 Paul Schaub
*
* This file is part of smack-omemo-signal.
*
* smack-omemo-signal is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jivesoftware.smack.omemo;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertNotSame;
import static junit.framework.TestCase.assertTrue;
import static junit.framework.TestCase.fail;
import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.signal.SignalOmemoKeyUtil;
import org.junit.Test;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
/**
* Test SignalOmemoKeyUtil methods.
*
* @author Paul Schaub
*/
public class LegacySignalOmemoKeyUtilTest extends SmackTestSuite {
private final SignalOmemoKeyUtil keyUtil = new SignalOmemoKeyUtil();
@Test
public void omemoIdentityKeyPairSerializationTest() {
IdentityKeyPair ikp = keyUtil.generateOmemoIdentityKeyPair();
byte[] bytes = keyUtil.identityKeyPairToBytes(ikp);
assertNotNull("serialized identityKeyPair must not be null.",
bytes);
assertNotSame("serialized identityKeyPair must not be of length 0.",
0, bytes.length);
try {
IdentityKeyPair ikp2 = keyUtil.identityKeyPairFromBytes(bytes);
assertTrue("Deserialized IdentityKeyPairs PublicKey must equal the originals one.",
ikp.getPublicKey().equals(ikp2.getPublicKey()));
} catch (CorruptedOmemoKeyException e) {
fail("Caught exception while deserializing IdentityKeyPair.");
}
}
@Test
public void omemoIdentityKeySerializationTest() {
IdentityKey k = keyUtil.generateOmemoIdentityKeyPair().getPublicKey();
try {
assertEquals("Deserialized IdentityKey must equal the original one.",
k, keyUtil.identityKeyFromBytes(keyUtil.identityKeyToBytes(k)));
} catch (CorruptedOmemoKeyException e) {
fail("Caught exception while serializing and deserializing identityKey (" + e + "): " + e.getMessage());
}
}
@Test
public void generateOmemoSignedPreKeyTest() {
IdentityKeyPair ikp = keyUtil.generateOmemoIdentityKeyPair();
try {
SignedPreKeyRecord spk = keyUtil.generateOmemoSignedPreKey(ikp, 1);
assertNotNull("SignedPreKey must not be null.", spk);
assertEquals("SignedPreKeyId must match.", 1, spk.getId());
assertEquals("singedPreKeyId must match here also.", 1, keyUtil.signedPreKeyIdFromKey(spk));
} catch (CorruptedOmemoKeyException e) {
fail("Caught an exception while generating signedPreKey (" + e + "): " + e.getMessage());
}
}
@Test
public void getFingerprintTest() {
IdentityKeyPair ikp = keyUtil.generateOmemoIdentityKeyPair();
IdentityKey ik = ikp.getPublicKey();
assertTrue("Length of fingerprint must be 64.",
keyUtil.getFingerprintOfIdentityKey(ik).length() == 64);
}
@Test
public void addressToDeviceTest() {
SignalProtocolAddress address = new SignalProtocolAddress("test@server.tld",1337);
try {
OmemoDevice device = keyUtil.addressAsOmemoDevice(address);
assertEquals(device, new OmemoDevice(JidCreate.bareFrom("test@server.tld"), 1337));
} catch (XmppStringprepException e) {
fail("Could not convert address to device: " + e + " " + e.getMessage());
}
}
@Test
public void deviceToAddressTest() {
try {
OmemoDevice device = new OmemoDevice(JidCreate.bareFrom("test@server.tld"), 1337);
SignalProtocolAddress address = keyUtil.omemoDeviceAsAddress(device);
assertEquals(address, new SignalProtocolAddress("test@server.tld", 1337));
} catch (XmppStringprepException e) {
fail("Could not convert device to address: " + e + " " + e.getMessage());
}
}
}

View file

@ -57,7 +57,12 @@ import org.junit.Test;
public class OmemoManagerTest extends SmackTestSuite {
@Test
public void instantiationTest() throws CorruptedOmemoKeyException, NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException, InterruptedException, XMPPException.XMPPErrorException, NoSuchPaddingException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchProviderException, IllegalBlockSizeException, SmackException {
public void instantiationTest()
throws CorruptedOmemoKeyException, NoSuchAlgorithmException, UnsupportedEncodingException,
InvalidKeyException, InterruptedException, XMPPException.XMPPErrorException, NoSuchPaddingException,
BadPaddingException, InvalidAlgorithmParameterException, NoSuchProviderException, IllegalBlockSizeException,
SmackException
{
SignalOmemoService.acknowledgeLicense();
SignalOmemoService.setup();
@ -73,8 +78,8 @@ public class OmemoManagerTest extends SmackTestSuite {
assertNotNull(c);
assertNotNull(d);
assertEquals(123, a.getDeviceId());
assertEquals(234, b.getDeviceId());
assertEquals(Integer.valueOf(123), a.getDeviceId());
assertEquals(Integer.valueOf(234), b.getDeviceId());
assertFalse(a == b);
assertFalse(a == c);

View file

@ -1,218 +0,0 @@
/**
*
* Copyright 2017 Paul Schaub
*
* This file is part of smack-omemo-signal.
*
* smack-omemo-signal is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jivesoftware.smack.omemo;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertNull;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertArrayEquals;
import static org.powermock.api.mockito.PowerMockito.when;
import java.io.File;
import java.util.Date;
import org.jivesoftware.smackx.omemo.FileBasedOmemoStore;
import org.jivesoftware.smackx.omemo.OmemoConfiguration;
import org.jivesoftware.smackx.omemo.OmemoManager;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.internal.CachedDeviceList;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.signal.SignalFileBasedOmemoStore;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
/**
* Test the file-based signalOmemoStore.
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({OmemoManager.class})
public class SignalFileBasedOmemoStoreTest {
private static File storePath;
private static SignalFileBasedOmemoStore omemoStore;
private static OmemoManager omemoManager;
private void deleteStore() {
FileBasedOmemoStore.deleteDirectory(storePath);
}
@BeforeClass
public static void setup() throws XmppStringprepException {
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");
}
OmemoConfiguration.setFileBasedOmemoStoreDefaultPath(storePath);
omemoStore = new SignalFileBasedOmemoStore();
OmemoDevice device = new OmemoDevice(JidCreate.bareFrom("storeTest@server.tld"), 55155);
omemoManager = PowerMockito.mock(OmemoManager.class);
when(omemoManager.getDeviceId()).thenReturn(device.getDeviceId());
when(omemoManager.getOwnJid()).thenReturn(device.getJid());
when(omemoManager.getOwnDevice()).thenReturn(device);
}
@Before
public void before() {
deleteStore();
}
@After
public void after() {
deleteStore();
}
@Test
public void isFreshInstallationTest() {
assertTrue(omemoStore.isFreshInstallation(omemoManager));
omemoStore.storeOmemoIdentityKeyPair(omemoManager, omemoStore.generateOmemoIdentityKeyPair());
assertFalse(omemoStore.isFreshInstallation(omemoManager));
omemoStore.purgeOwnDeviceKeys(omemoManager);
assertTrue(omemoStore.isFreshInstallation(omemoManager));
}
@Test
public void defaultDeviceIdTest() throws XmppStringprepException {
assertEquals(-1, omemoStore.getDefaultDeviceId(omemoManager.getOwnJid()));
omemoStore.setDefaultDeviceId(omemoManager.getOwnJid(), 55);
assertEquals(55, omemoStore.getDefaultDeviceId(omemoManager.getOwnJid()));
assertEquals(-1, omemoStore.getDefaultDeviceId(JidCreate.bareFrom("randomGuy@server.tld")));
}
@Test
public void cachedDeviceListTest() throws XmppStringprepException {
OmemoDevice bob = new OmemoDevice(JidCreate.bareFrom("bob@builder.tv"), 666);
OmemoDevice craig = new OmemoDevice(JidCreate.bareFrom("craig@southpark.tv"), 3333333);
CachedDeviceList bobsList = new CachedDeviceList();
assertEquals(0, bobsList.getAllDevices().size());
bobsList.getActiveDevices().add(bob.getDeviceId());
bobsList.getActiveDevices().add(777);
bobsList.getInactiveDevices().add(888);
CachedDeviceList craigsList = new CachedDeviceList();
craigsList.addDevice(craig.getDeviceId());
assertEquals(3, bobsList.getAllDevices().size());
assertEquals(2, bobsList.getActiveDevices().size());
assertTrue(bobsList.getInactiveDevices().contains(888));
assertTrue(bobsList.getActiveDevices().contains(777));
assertTrue(bobsList.getAllDevices().contains(888));
assertEquals(0, craigsList.getInactiveDevices().size());
assertEquals(1, craigsList.getActiveDevices().size());
assertEquals(1, craigsList.getAllDevices().size());
assertEquals(craig.getDeviceId(), craigsList.getActiveDevices().iterator().next().intValue());
}
@Test
public void omemoIdentityKeyPairTest() throws CorruptedOmemoKeyException {
assertNull(omemoStore.loadOmemoIdentityKeyPair(omemoManager));
omemoStore.storeOmemoIdentityKeyPair(omemoManager, omemoStore.generateOmemoIdentityKeyPair());
IdentityKeyPair ikp = omemoStore.loadOmemoIdentityKeyPair(omemoManager);
assertNotNull(ikp);
assertTrue(omemoStore.keyUtil().getFingerprint(ikp.getPublicKey()).equals(omemoStore.getFingerprint(omemoManager)));
}
@Test
public void signedPreKeyTest() throws CorruptedOmemoKeyException {
assertEquals(0, omemoStore.loadOmemoSignedPreKeys(omemoManager).size());
IdentityKeyPair ikp = omemoStore.generateOmemoIdentityKeyPair();
SignedPreKeyRecord spk = omemoStore.generateOmemoSignedPreKey(ikp, 14);
omemoStore.storeOmemoSignedPreKey(omemoManager, 14, spk);
assertEquals(1, omemoStore.loadOmemoSignedPreKeys(omemoManager).size());
assertNotNull(omemoStore.loadOmemoSignedPreKey(omemoManager, 14));
assertArrayEquals(spk.serialize(), omemoStore.loadOmemoSignedPreKey(omemoManager, 14).serialize());
assertNull(omemoStore.loadOmemoSignedPreKey(omemoManager, 13));
assertEquals(0, omemoStore.loadCurrentSignedPreKeyId(omemoManager));
omemoStore.storeCurrentSignedPreKeyId(omemoManager, 15);
assertEquals(15, omemoStore.loadCurrentSignedPreKeyId(omemoManager));
omemoStore.removeOmemoSignedPreKey(omemoManager, 14);
assertNull(omemoStore.loadOmemoSignedPreKey(omemoManager, 14));
assertNull(omemoStore.getDateOfLastSignedPreKeyRenewal(omemoManager));
Date now = new Date();
omemoStore.setDateOfLastSignedPreKeyRenewal(omemoManager, now);
assertEquals(now, omemoStore.getDateOfLastSignedPreKeyRenewal(omemoManager));
}
@Test
public void preKeyTest() {
assertEquals(0, omemoStore.loadOmemoPreKeys(omemoManager).size());
assertNull(omemoStore.loadOmemoPreKey(omemoManager, 12));
omemoStore.storeOmemoPreKeys(omemoManager,
omemoStore.generateOmemoPreKeys(1, 20));
assertNotNull(omemoStore.loadOmemoPreKey(omemoManager, 12));
assertEquals(20, omemoStore.loadOmemoPreKeys(omemoManager).size());
omemoStore.removeOmemoPreKey(omemoManager, 12);
assertNull(omemoStore.loadOmemoPreKey(omemoManager, 12));
assertEquals(19, omemoStore.loadOmemoPreKeys(omemoManager).size());
assertEquals(0, omemoStore.loadLastPreKeyId(omemoManager));
omemoStore.storeLastPreKeyId(omemoManager, 35);
assertEquals(35, omemoStore.loadLastPreKeyId(omemoManager));
}
@Test
public void trustingTest() throws XmppStringprepException, CorruptedOmemoKeyException {
OmemoDevice bob = new OmemoDevice(JidCreate.bareFrom("bob@builder.tv"), 555);
IdentityKey bobsKey = omemoStore.generateOmemoIdentityKeyPair().getPublicKey();
assertFalse(omemoStore.isDecidedOmemoIdentity(omemoManager, bob, bobsKey));
assertFalse(omemoStore.isTrustedOmemoIdentity(omemoManager, bob, bobsKey));
omemoStore.trustOmemoIdentity(omemoManager, bob, bobsKey);
assertTrue(omemoStore.isDecidedOmemoIdentity(omemoManager, bob, omemoStore.keyUtil().getFingerprint(bobsKey)));
assertTrue(omemoStore.isTrustedOmemoIdentity(omemoManager, bob, omemoStore.keyUtil().getFingerprint(bobsKey)));
assertNull(omemoStore.loadOmemoIdentityKey(omemoManager, bob));
omemoStore.storeOmemoIdentityKey(omemoManager, bob, bobsKey);
assertNotNull(omemoStore.loadOmemoIdentityKey(omemoManager, bob));
IdentityKey bobsOtherKey = omemoStore.generateOmemoIdentityKeyPair().getPublicKey();
assertFalse(omemoStore.isTrustedOmemoIdentity(omemoManager, bob, bobsOtherKey));
assertFalse(omemoStore.isDecidedOmemoIdentity(omemoManager, bob, bobsOtherKey));
omemoStore.distrustOmemoIdentity(omemoManager, bob, omemoStore.keyUtil().getFingerprint(bobsKey));
assertTrue(omemoStore.isDecidedOmemoIdentity(omemoManager, bob, bobsKey));
assertFalse(omemoStore.isTrustedOmemoIdentity(omemoManager, bob, bobsKey));
assertNull(omemoStore.getDateOfLastReceivedMessage(omemoManager, bob));
Date now = new Date();
omemoStore.setDateOfLastReceivedMessage(omemoManager, bob, now);
assertEquals(now, omemoStore.getDateOfLastReceivedMessage(omemoManager, bob));
}
}

File diff suppressed because one or more lines are too long

View file

@ -41,6 +41,6 @@ public class SignalOmemoStoreConnectorTest {
@Test
public void isTrustedIdentityTest() {
SignalOmemoStoreConnector connector = new SignalOmemoStoreConnector(null, null);
assertTrue("All identities must be trusted by default.", connector.isTrustedIdentity(null, null));
assertTrue("All identities must be trusted by default.", connector.isTrustedIdentity(null, null, null));
}
}

View file

@ -0,0 +1,84 @@
/**
*
* Copyright 2017 Paul Schaub
*
* This file is part of smack-omemo-signal.
*
* smack-omemo-signal is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jivesoftware.smack.omemo;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import org.jivesoftware.smackx.omemo.OmemoStore;
import org.jivesoftware.smackx.omemo.signal.SignalCachingOmemoStore;
import org.jivesoftware.smackx.omemo.signal.SignalFileBasedOmemoStore;
import org.jivesoftware.smackx.omemo.signal.SignalOmemoKeyUtil;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.jxmpp.stringprep.XmppStringprepException;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.SessionCipher;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.state.PreKeyBundle;
import org.whispersystems.libsignal.state.PreKeyRecord;
import org.whispersystems.libsignal.state.SessionRecord;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
/**
* smack-omemo-signal implementation of {@link OmemoStoreTest}.
* This class executes tests of its super class with available implementations of {@link OmemoStore}.
* So far this includes {@link SignalFileBasedOmemoStore}, {@link SignalCachingOmemoStore}.
*/
@RunWith(value = Parameterized.class)
public class SignalOmemoStoreTest extends OmemoStoreTest<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> {
public SignalOmemoStoreTest(OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> store)
throws XmppStringprepException {
super(store);
}
/**
* We are running this Test with multiple available OmemoStore implementations.
* @return
* @throws IOException
*/
@Parameterized.Parameters
public static Collection<Object[]> getParameters() throws IOException {
TemporaryFolder temp = initStaticTemp();
return Arrays.asList(new Object[][] {
// Simple file based store
{ new SignalFileBasedOmemoStore(temp.newFolder("sigFileBased"))},
// Ephemeral caching store
{ new SignalCachingOmemoStore()},
// Caching file based store
{ new SignalCachingOmemoStore(new SignalFileBasedOmemoStore(temp.newFolder("cachingSigFileBased")))}
});
}
@Test
public void keyUtilTest() {
assertTrue(store.keyUtil() instanceof SignalOmemoKeyUtil);
}
}

View file

@ -0,0 +1,423 @@
/**
*
* 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 java.util.Date;
import java.util.HashMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.internal.CachedDeviceList;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.util.OmemoKeyUtil;
import org.jxmpp.jid.BareJid;
/**
* This class implements the Proxy Pattern in order to wrap an OmemoStore with a caching layer.
* This reduces access to the underlying storage layer (eg. database, filesystem) by only accessing it for
* missing/updated values.
*
* Alternatively this implementation can be used as an ephemeral keystore without a persisting backend.
*
* @param <T_IdKeyPair>
* @param <T_IdKey>
* @param <T_PreKey>
* @param <T_SigPreKey>
* @param <T_Sess>
* @param <T_Addr>
* @param <T_ECPub>
* @param <T_Bundle>
* @param <T_Ciph>
*/
public class CachingOmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
extends OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
{
private final HashMap<OmemoDevice, KeyCache<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess>> caches = new HashMap<>();
private final OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> persistent;
private final OmemoKeyUtil<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> keyUtil;
public CachingOmemoStore(OmemoKeyUtil<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> keyUtil) {
if (keyUtil == null) {
throw new IllegalArgumentException("KeyUtil MUST NOT be null!");
}
this.keyUtil = keyUtil;
persistent = null;
}
public CachingOmemoStore(OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> wrappedStore) {
this.keyUtil = null;
persistent = wrappedStore;
}
@Override
public SortedSet<Integer> localDeviceIdsOf(BareJid localUser) {
if (persistent != null) {
return persistent.localDeviceIdsOf(localUser);
} else {
return new TreeSet<>(); //TODO: ?
}
}
@Override
public T_IdKeyPair loadOmemoIdentityKeyPair(OmemoDevice userDevice)
throws CorruptedOmemoKeyException
{
T_IdKeyPair pair = getCache(userDevice).identityKeyPair;
if (pair == null && persistent != null) {
pair = persistent.loadOmemoIdentityKeyPair(userDevice);
if (pair != null) {
getCache(userDevice).identityKeyPair = pair;
}
}
return pair;
}
@Override
public void storeOmemoIdentityKeyPair(OmemoDevice userDevice, T_IdKeyPair identityKeyPair) {
getCache(userDevice).identityKeyPair = identityKeyPair;
if (persistent != null) {
persistent.storeOmemoIdentityKeyPair(userDevice, identityKeyPair);
}
}
@Override
public void removeOmemoIdentityKeyPair(OmemoDevice userDevice) {
getCache(userDevice).identityKeyPair = null;
if (persistent != null) {
persistent.removeOmemoIdentityKeyPair(userDevice);
}
}
@Override
public T_IdKey loadOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice)
throws CorruptedOmemoKeyException
{
T_IdKey idKey = getCache(userDevice).identityKeys.get(contactsDevice);
if (idKey == null && persistent != null) {
idKey = persistent.loadOmemoIdentityKey(userDevice, contactsDevice);
if (idKey != null) {
getCache(userDevice).identityKeys.put(contactsDevice, idKey);
}
}
return idKey;
}
@Override
public void storeOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice device, T_IdKey t_idKey) {
getCache(userDevice).identityKeys.put(device, t_idKey);
if (persistent != null) {
persistent.storeOmemoIdentityKey(userDevice, device, t_idKey);
}
}
@Override
public void removeOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice) {
getCache(userDevice).identityKeys.remove(contactsDevice);
if (persistent != null) {
persistent.removeOmemoIdentityKey(userDevice, contactsDevice);
}
}
@Override
public void setDateOfLastReceivedMessage(OmemoDevice userDevice, OmemoDevice from, Date date) {
getCache(userDevice).lastMessagesDates.put(from, date);
if (persistent != null) {
persistent.setDateOfLastReceivedMessage(userDevice, from, date);
}
}
@Override
public Date getDateOfLastReceivedMessage(OmemoDevice userDevice, OmemoDevice from) {
Date last = getCache(userDevice).lastMessagesDates.get(from);
if (last == null && persistent != null) {
last = persistent.getDateOfLastReceivedMessage(userDevice, from);
if (last != null) {
getCache(userDevice).lastMessagesDates.put(from, last);
}
}
return last;
}
@Override
public void setDateOfLastSignedPreKeyRenewal(OmemoDevice userDevice, Date date) {
getCache(userDevice).lastRenewalDate = date;
if (persistent != null) {
persistent.setDateOfLastSignedPreKeyRenewal(userDevice, date);
}
}
@Override
public Date getDateOfLastSignedPreKeyRenewal(OmemoDevice userDevice) {
Date lastRenewal = getCache(userDevice).lastRenewalDate;
if (lastRenewal == null && persistent != null) {
lastRenewal = persistent.getDateOfLastSignedPreKeyRenewal(userDevice);
if (lastRenewal != null) {
getCache(userDevice).lastRenewalDate = lastRenewal;
}
}
return lastRenewal;
}
@Override
public T_PreKey loadOmemoPreKey(OmemoDevice userDevice, int preKeyId) {
T_PreKey preKey = getCache(userDevice).preKeys.get(preKeyId);
if (preKey == null && persistent != null) {
preKey = persistent.loadOmemoPreKey(userDevice, preKeyId);
if (preKey != null) {
getCache(userDevice).preKeys.put(preKeyId, preKey);
}
}
return preKey;
}
@Override
public void storeOmemoPreKey(OmemoDevice userDevice, int preKeyId, T_PreKey t_preKey) {
getCache(userDevice).preKeys.put(preKeyId, t_preKey);
if (persistent != null) {
persistent.storeOmemoPreKey(userDevice, preKeyId, t_preKey);
}
}
@Override
public void removeOmemoPreKey(OmemoDevice userDevice, int preKeyId) {
getCache(userDevice).preKeys.remove(preKeyId);
if (persistent != null) {
persistent.removeOmemoPreKey(userDevice, preKeyId);
}
}
@Override
public TreeMap<Integer, T_PreKey> loadOmemoPreKeys(OmemoDevice userDevice) {
TreeMap<Integer, T_PreKey> preKeys = getCache(userDevice).preKeys;
if (preKeys.isEmpty() && persistent != null) {
preKeys.putAll(persistent.loadOmemoPreKeys(userDevice));
}
return new TreeMap<>(preKeys);
}
@Override
public T_SigPreKey loadOmemoSignedPreKey(OmemoDevice userDevice, int signedPreKeyId) {
T_SigPreKey sigPreKey = getCache(userDevice).signedPreKeys.get(signedPreKeyId);
if (sigPreKey == null && persistent != null) {
sigPreKey = persistent.loadOmemoSignedPreKey(userDevice, signedPreKeyId);
if (sigPreKey != null) {
getCache(userDevice).signedPreKeys.put(signedPreKeyId, sigPreKey);
}
}
return sigPreKey;
}
@Override
public TreeMap<Integer, T_SigPreKey> loadOmemoSignedPreKeys(OmemoDevice userDevice) {
TreeMap<Integer, T_SigPreKey> sigPreKeys = getCache(userDevice).signedPreKeys;
if (sigPreKeys.isEmpty() && persistent != null) {
sigPreKeys.putAll(persistent.loadOmemoSignedPreKeys(userDevice));
}
return new TreeMap<>(sigPreKeys);
}
@Override
public void storeOmemoSignedPreKey(OmemoDevice userDevice,
int signedPreKeyId,
T_SigPreKey signedPreKey) {
getCache(userDevice).signedPreKeys.put(signedPreKeyId, signedPreKey);
if (persistent != null) {
persistent.storeOmemoSignedPreKey(userDevice, signedPreKeyId, signedPreKey);
}
}
@Override
public void removeOmemoSignedPreKey(OmemoDevice userDevice, int signedPreKeyId) {
getCache(userDevice).signedPreKeys.remove(signedPreKeyId);
if (persistent != null) {
persistent.removeOmemoSignedPreKey(userDevice, signedPreKeyId);
}
}
@Override
public T_Sess loadRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice) {
HashMap<Integer, T_Sess> contactSessions = getCache(userDevice).sessions.get(contactsDevice.getJid());
if (contactSessions == null) {
contactSessions = new HashMap<>();
getCache(userDevice).sessions.put(contactsDevice.getJid(), contactSessions);
}
T_Sess session = contactSessions.get(contactsDevice.getDeviceId());
if (session == null && persistent != null) {
session = persistent.loadRawSession(userDevice, contactsDevice);
if (session != null) {
contactSessions.put(contactsDevice.getDeviceId(), session);
}
}
return session;
}
@Override
public HashMap<Integer, T_Sess> loadAllRawSessionsOf(OmemoDevice userDevice, BareJid contact) {
HashMap<Integer, T_Sess> sessions = getCache(userDevice).sessions.get(contact);
if (sessions == null) {
sessions = new HashMap<>();
getCache(userDevice).sessions.put(contact, sessions);
}
if (sessions.isEmpty() && persistent != null) {
sessions.putAll(persistent.loadAllRawSessionsOf(userDevice, contact));
}
return new HashMap<>(sessions);
}
@Override
public void storeRawSession(OmemoDevice userDevice, OmemoDevice contactsDevicece, T_Sess session) {
HashMap<Integer, T_Sess> sessions = getCache(userDevice).sessions.get(contactsDevicece.getJid());
if (sessions == null) {
sessions = new HashMap<>();
getCache(userDevice).sessions.put(contactsDevicece.getJid(), sessions);
}
sessions.put(contactsDevicece.getDeviceId(), session);
if (persistent != null) {
persistent.storeRawSession(userDevice, contactsDevicece, session);
}
}
@Override
public void removeRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice) {
HashMap<Integer, T_Sess> sessions = getCache(userDevice).sessions.get(contactsDevice.getJid());
if (sessions != null) {
sessions.remove(contactsDevice.getDeviceId());
}
if (persistent != null) {
persistent.removeRawSession(userDevice, contactsDevice);
}
}
@Override
public void removeAllRawSessionsOf(OmemoDevice userDevice, BareJid contact) {
getCache(userDevice).sessions.remove(contact);
if (persistent != null) {
persistent.removeAllRawSessionsOf(userDevice, contact);
}
}
@Override
public boolean containsRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice) {
HashMap<Integer, T_Sess> sessions = getCache(userDevice).sessions.get(contactsDevice.getJid());
return (sessions != null && sessions.get(contactsDevice.getDeviceId()) != null) ||
(persistent != null && persistent.containsRawSession(userDevice, contactsDevice));
}
@Override
public CachedDeviceList loadCachedDeviceList(OmemoDevice userDevice, BareJid contact) {
CachedDeviceList list = getCache(userDevice).deviceLists.get(contact);
if (list == null && persistent != null) {
list = persistent.loadCachedDeviceList(userDevice, contact);
if (list != null) {
getCache(userDevice).deviceLists.put(contact, list);
}
}
return list == null ? new CachedDeviceList() : new CachedDeviceList(list);
}
@Override
public void storeCachedDeviceList(OmemoDevice userDevice,
BareJid contact,
CachedDeviceList deviceList) {
getCache(userDevice).deviceLists.put(contact, new CachedDeviceList(deviceList));
if (persistent != null) {
persistent.storeCachedDeviceList(userDevice, contact, deviceList);
}
}
@Override
public void purgeOwnDeviceKeys(OmemoDevice userDevice) {
caches.remove(userDevice);
if (persistent != null) {
persistent.purgeOwnDeviceKeys(userDevice);
}
}
@Override
public OmemoKeyUtil<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
keyUtil() {
if (persistent != null) {
return persistent.keyUtil();
} else {
return keyUtil;
}
}
/**
* Return the {@link KeyCache} object of an {@link OmemoManager}.
* @param device
* @return
*/
private KeyCache<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess> getCache(OmemoDevice device) {
KeyCache<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess> cache = caches.get(device);
if (cache == null) {
cache = new KeyCache<>();
caches.put(device, cache);
}
return cache;
}
/**
* Cache that stores values for an {@link OmemoManager}.
* @param <T_IdKeyPair>
* @param <T_IdKey>
* @param <T_PreKey>
* @param <T_SigPreKey>
* @param <T_Sess>
*/
private static class KeyCache<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess> {
private T_IdKeyPair identityKeyPair;
private final TreeMap<Integer, T_PreKey> preKeys = new TreeMap<>();
private final TreeMap<Integer, T_SigPreKey> signedPreKeys = new TreeMap<>();
private final HashMap<BareJid, HashMap<Integer, T_Sess>> sessions = new HashMap<>();
private final HashMap<OmemoDevice, T_IdKey> identityKeys = new HashMap<>();
private final HashMap<OmemoDevice, Date> lastMessagesDates = new HashMap<>();
private final HashMap<BareJid, CachedDeviceList> deviceLists = new HashMap<>();
private Date lastRenewalDate = null;
}
}

View file

@ -39,10 +39,6 @@ public class OmemoFingerprint implements CharSequence {
return fingerprintString.subSequence(start, end);
}
public CharSequence subSequence(int start) {
return fingerprintString.subSequence(start, fingerprintString.length() - 1);
}
@Override
public String toString() {
return fingerprintString;

View file

@ -27,22 +27,26 @@ import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.AbstractConnectionListener;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.StanzaListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.NamedElement;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.Async;
import org.jivesoftware.smackx.carbons.CarbonCopyReceivedListener;
import org.jivesoftware.smackx.carbons.CarbonManager;
import org.jivesoftware.smackx.carbons.packet.CarbonExtension;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.eme.element.ExplicitMessageEncryptionElement;
import org.jivesoftware.smackx.hints.element.StoreHint;
@ -66,6 +70,8 @@ import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation;
import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener;
import org.jivesoftware.smackx.omemo.listener.OmemoMucMessageListener;
import org.jivesoftware.smackx.omemo.trust.TrustCallback;
import org.jivesoftware.smackx.omemo.trust.TrustState;
import org.jivesoftware.smackx.pep.PEPListener;
import org.jivesoftware.smackx.pep.PEPManager;
import org.jivesoftware.smackx.pubsub.EventElement;
@ -78,8 +84,6 @@ import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.EntityFullJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
/**
* Manager that allows sending messages encrypted with OMEMO.
@ -91,64 +95,54 @@ import org.jxmpp.stringprep.XmppStringprepException;
public final class OmemoManager extends Manager {
private static final Logger LOGGER = Logger.getLogger(OmemoManager.class.getName());
private static final WeakHashMap<XMPPConnection, WeakHashMap<Integer,OmemoManager>> INSTANCES = new WeakHashMap<>();
private static final WeakHashMap<XMPPConnection, TreeMap<Integer,OmemoManager>> INSTANCES = new WeakHashMap<>();
private final OmemoService<?, ?, ?, ?, ?, ?, ?, ?, ?> service;
private final Object LOCK = new Object();
private final HashSet<OmemoMessageListener> omemoMessageListeners = new HashSet<>();
private final HashSet<OmemoMucMessageListener> omemoMucMessageListeners = new HashSet<>();
private OmemoService<?,?,?,?,?,?,?,?,?>.OmemoStanzaListener omemoStanzaListener;
private OmemoService<?,?,?,?,?,?,?,?,?>.OmemoCarbonCopyListener omemoCarbonCopyListener;
private TrustCallback trustCallback;
private int deviceId;
private BareJid ownJid;
private Integer deviceId;
/**
* Private constructor to prevent multiple instances on a single connection (which probably would be bad!).
*
* @param connection connection
*/
private OmemoManager(XMPPConnection connection, int deviceId) {
private OmemoManager(XMPPConnection connection, Integer deviceId) {
super(connection);
service = OmemoService.getInstance();
if (deviceId != null && deviceId <= 0) {
throw new IllegalArgumentException("DeviceId MUST be greater than 0.");
}
this.deviceId = deviceId;
connection.addConnectionListener(new AbstractConnectionListener() {
@Override
public void authenticated(XMPPConnection connection, boolean resumed) {
if (resumed) {
return;
}
Async.go(new Runnable() {
@Override
public void run() {
try {
initialize();
} catch (InterruptedException | CorruptedOmemoKeyException | PubSubException.NotALeafNodeException | SmackException.NotLoggedInException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) {
LOGGER.log(Level.SEVERE, "connectionListener.authenticated() failed to initialize OmemoManager: "
+ e.getMessage());
}
}
});
}
});
// StanzaListeners
startStanzaListeners();
PEPManager.getInstanceFor(connection).addPEPListener(deviceListUpdateListener);
// Announce OMEMO support
ServiceDiscoveryManager.getInstanceFor(connection).addFeature(PEP_NODE_DEVICE_LIST_NOTIFY);
service = OmemoService.getInstance();
}
/**
* Get an instance of the OmemoManager for the given connection and deviceId.
*
* @param connection Connection
* @param deviceId deviceId of the Manager. If the deviceId is null, a random id will be generated.
* @return an OmemoManager
* Return an OmemoManager instance for the given connection and deviceId.
* If there was an OmemoManager for the connection and id before, return it. Otherwise create a new OmemoManager
* instance and return it.
* @param connection XmppConnection.
* @param deviceId can be null, to generate a random deviceId. DeviceIds MUST NOT be less than 1.
* @return
*/
public static synchronized OmemoManager getInstanceFor(XMPPConnection connection, Integer deviceId) {
WeakHashMap<Integer,OmemoManager> managersOfConnection = INSTANCES.get(connection);
public synchronized static OmemoManager getInstanceFor(XMPPConnection connection, Integer deviceId) {
TreeMap<Integer,OmemoManager> managersOfConnection = INSTANCES.get(connection);
if (managersOfConnection == null) {
managersOfConnection = new WeakHashMap<>();
managersOfConnection = new TreeMap<>();
INSTANCES.put(connection, managersOfConnection);
}
@ -161,42 +155,50 @@ public final class OmemoManager extends Manager {
manager = new OmemoManager(connection, deviceId);
managersOfConnection.put(deviceId, manager);
}
return manager;
}
/**
* Get an instance of the OmemoManager for the given connection.
* This method creates the OmemoManager for the stored defaultDeviceId of the connections user.
* If there is no such id is stored, it uses a fresh deviceId and sets that as defaultDeviceId instead.
*
* @param connection connection
* @return OmemoManager
* Returns an OmemoManager instance for the given connection. If there was no OmemoManager for that connection
* before, create a new one. If there was one before, return it. If there were multiple managers before, return the
* one with the lowest deviceId.
* @param connection XmppConnection.
* @return manager
*/
public static synchronized OmemoManager getInstanceFor(XMPPConnection connection) {
BareJid user;
if (connection.getUser() != null) {
user = connection.getUser().asBareJid();
public synchronized static OmemoManager getInstanceFor(XMPPConnection connection) {
TreeMap<Integer, OmemoManager> managers = INSTANCES.get(connection);
if (managers == null) {
managers = new TreeMap<>();
INSTANCES.put(connection, managers);
}
OmemoManager manager;
if (managers.size() == 0) {
manager = new OmemoManager(connection, null);
managers.put(null, manager);
} else {
// This might be dangerous
try {
user = JidCreate.bareFrom(((AbstractXMPPConnection) connection).getConfiguration().getUsername());
} catch (XmppStringprepException e) {
throw new AssertionError("Username is not a valid Jid. " +
"Use OmemoManager.gerInstanceFor(Connection, deviceId) instead.");
}
manager = managers.get(managers.firstKey());
}
int defaultDeviceId = OmemoService.getInstance().getOmemoStoreBackend().getDefaultDeviceId(user);
if (defaultDeviceId < 1) {
defaultDeviceId = randomDeviceId();
OmemoService.getInstance().getOmemoStoreBackend().setDefaultDeviceId(user, defaultDeviceId);
}
return getInstanceFor(connection, defaultDeviceId);
return manager;
}
/**
* Initializes the OmemoManager. This method is called automatically once the client logs into the server successfully.
* Set a TrustCallback for this particular OmemoManager.
* @param callback trustCallback used to query and modify trust states.
*/
public void setTrustCallback(TrustCallback callback) {
if (trustCallback != null) {
throw new IllegalStateException("TrustCallback can only be set once.");
}
trustCallback = callback;
}
/**
* Initializes the OmemoManager. This method must be called before the manager can be used.
*
* @throws CorruptedOmemoKeyException
* @throws InterruptedException
@ -206,14 +208,48 @@ public final class OmemoManager extends Manager {
* @throws SmackException.NotLoggedInException
* @throws PubSubException.NotALeafNodeException
*/
public void initialize() throws CorruptedOmemoKeyException, InterruptedException, SmackException.NoResponseException,
SmackException.NotConnectedException, XMPPException.XMPPErrorException, SmackException.NotLoggedInException,
public void initialize()
throws SmackException.NotLoggedInException, CorruptedOmemoKeyException, InterruptedException,
SmackException.NoResponseException, SmackException.NotConnectedException, XMPPException.XMPPErrorException,
PubSubException.NotALeafNodeException {
getOmemoService().initialize(this);
synchronized (LOCK) {
if (!connection().isAuthenticated()) {
throw new SmackException.NotLoggedInException();
}
// Set jid and deviceId
this.ownJid = connection().getUser().asBareJid();
if (deviceId == null) {
SortedSet<Integer> localDeviceIds = getOmemoService().getOmemoStoreBackend().localDeviceIdsOf(ownJid);
setDeviceId(localDeviceIds.size() > 0 ? localDeviceIds.first() : randomDeviceId());
}
getOmemoService().initialize(new KnownBareJidGuard(this));
}
}
/**
* Initialize the manager without blocking. Once the manager is successfully initialized, the finishedCallback will
* be notified. It will also get notified, if an error occurs.
* @param finishedCallback
*/
public void initializeAsync(final FinishedCallback finishedCallback) {
Async.go(new Runnable() {
@Override
public void run() {
try {
initialize();
finishedCallback.initializationFinished(OmemoManager.this);
} catch (Exception e) {
finishedCallback.initializationFailed(e);
}
}
});
}
/**
* OMEMO encrypt a cleartext message for a single recipient.
* Note that this method does NOT set the 'to' attribute of the message.
*
* @param to recipients barejid
* @param message text to encrypt
@ -222,16 +258,24 @@ public final class OmemoManager extends Manager {
* @throws UndecidedOmemoIdentityException When there are undecided devices
* @throws NoSuchAlgorithmException
* @throws InterruptedException
* @throws CannotEstablishOmemoSessionException when we could not create session withs all of the recipients devices.
* @throws CannotEstablishOmemoSessionException when we could not create session withs all of the recipients
* devices.
* @throws SmackException.NotConnectedException
* @throws SmackException.NoResponseException
*/
public Message encrypt(BareJid to, String message) throws CryptoFailedException, UndecidedOmemoIdentityException, NoSuchAlgorithmException, InterruptedException, CannotEstablishOmemoSessionException, SmackException.NotConnectedException, SmackException.NoResponseException {
Message m = new Message();
m.setBody(message);
OmemoVAxolotlElement encrypted = getOmemoService().processSendingMessage(this, to, m);
public Message encrypt(BareJid to, String message)
throws CryptoFailedException, UndecidedOmemoIdentityException, NoSuchAlgorithmException,
InterruptedException, CannotEstablishOmemoSessionException, SmackException.NotConnectedException,
SmackException.NoResponseException, SmackException.NotLoggedInException
{
synchronized (LOCK) {
KnownBareJidGuard guard = new KnownBareJidGuard(this);
Message plaintext = new Message();
plaintext.setBody(message);
OmemoVAxolotlElement encrypted = getOmemoService().processSendingMessage(guard, to, plaintext);
return finishMessage(encrypted);
}
}
/**
* OMEMO encrypt a cleartext message for multiple recipients.
@ -248,12 +292,19 @@ public final class OmemoManager extends Manager {
* @throws SmackException.NotConnectedException
* @throws SmackException.NoResponseException
*/
public Message encrypt(ArrayList<BareJid> recipients, String message) throws CryptoFailedException, UndecidedOmemoIdentityException, NoSuchAlgorithmException, InterruptedException, CannotEstablishOmemoSessionException, SmackException.NotConnectedException, SmackException.NoResponseException {
public Message encrypt(ArrayList<BareJid> recipients, String message)
throws CryptoFailedException, UndecidedOmemoIdentityException, NoSuchAlgorithmException,
InterruptedException, CannotEstablishOmemoSessionException, SmackException.NotConnectedException,
SmackException.NoResponseException, SmackException.NotLoggedInException
{
synchronized (LOCK) {
Message m = new Message();
m.setBody(message);
OmemoVAxolotlElement encrypted = getOmemoService().processSendingMessage(this, recipients, m);
OmemoVAxolotlElement encrypted = getOmemoService().processSendingMessage(
new KnownBareJidGuard(this), recipients, m);
return finishMessage(encrypted);
}
}
/**
* Encrypt a message for all recipients in the MultiUserChat.
@ -272,18 +323,27 @@ public final class OmemoManager extends Manager {
* @throws CannotEstablishOmemoSessionException when there is a user for whom we could not create a session
* with any of their devices.
*/
public Message encrypt(MultiUserChat muc, String message) throws UndecidedOmemoIdentityException, NoSuchAlgorithmException, CryptoFailedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, NoOmemoSupportException, CannotEstablishOmemoSessionException {
public Message encrypt(MultiUserChat muc, String message)
throws UndecidedOmemoIdentityException, NoSuchAlgorithmException, CryptoFailedException,
XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException, NoOmemoSupportException, CannotEstablishOmemoSessionException,
SmackException.NotLoggedInException
{
synchronized (LOCK) {
if (!multiUserChatSupportsOmemo(muc.getRoom())) {
throw new NoOmemoSupportException();
}
Message m = new Message();
m.setBody(message);
ArrayList<BareJid> recipients = new ArrayList<>();
for (EntityFullJid e : muc.getOccupants()) {
recipients.add(muc.getOccupant(e).getJid().asBareJid());
}
return encrypt(recipients, message);
}
}
/**
* Encrypt a message for all users we could build a session with successfully in a previous attempt.
@ -296,12 +356,17 @@ public final class OmemoManager extends Manager {
* @throws CryptoFailedException
* @throws UndecidedOmemoIdentityException when there are undecided identities.
*/
public Message encryptForExistingSessions(CannotEstablishOmemoSessionException exception, String message) throws CryptoFailedException, UndecidedOmemoIdentityException {
public Message encryptForExistingSessions(CannotEstablishOmemoSessionException exception, String message)
throws CryptoFailedException, UndecidedOmemoIdentityException, SmackException.NotLoggedInException
{
synchronized (LOCK) {
Message m = new Message();
m.setBody(message);
OmemoVAxolotlElement encrypted = getOmemoService().encryptOmemoMessage(this, exception.getSuccesses(), m);
OmemoVAxolotlElement encrypted = getOmemoService()
.encryptOmemoMessage(new KnownBareJidGuard(this), exception.getSuccesses(), m);
return finishMessage(encrypted);
}
}
/**
* Decrypt an OMEMO message. This method comes handy when dealing with messages that were not automatically
@ -317,13 +382,19 @@ public final class OmemoManager extends Manager {
* @throws CorruptedOmemoKeyException When the used keys are invalid
* @throws NoRawSessionException When there is no double ratchet session found for this message
*/
public ClearTextMessage decrypt(BareJid sender, Message omemoMessage) throws InterruptedException, SmackException.NoResponseException, SmackException.NotConnectedException, CryptoFailedException, XMPPException.XMPPErrorException, CorruptedOmemoKeyException, NoRawSessionException {
return getOmemoService().processLocalMessage(this, sender, omemoMessage);
public ClearTextMessage decrypt(BareJid sender, Message omemoMessage)
throws InterruptedException, SmackException.NoResponseException, SmackException.NotConnectedException,
CryptoFailedException, XMPPException.XMPPErrorException, CorruptedOmemoKeyException, NoRawSessionException,
SmackException.NotLoggedInException
{
synchronized (LOCK) {
return getOmemoService().processLocalMessage(new KnownBareJidGuard(this), sender, omemoMessage);
}
}
/**
* Return a list of all OMEMO messages that were found in the MAM query result, that could be successfully decrypted.
* Normal cleartext messages are also added to this list.
* Return a list of all OMEMO messages that were found in the MAM query result, that could be successfully
* decrypted. Normal cleartext messages are also added to this list.
*
* @param mamQueryResult mamQueryResult
* @return list of decrypted OmemoMessages
@ -332,11 +403,16 @@ public final class OmemoManager extends Manager {
* @throws SmackException.NotConnectedException Exception
* @throws SmackException.NoResponseException Exception
*/
public List<ClearTextMessage> decryptMamQueryResult(MamManager.MamQueryResult mamQueryResult) throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException {
public List<ClearTextMessage> decryptMamQueryResult(MamManager.MamQueryResult mamQueryResult)
throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException,
SmackException.NoResponseException, SmackException.NotLoggedInException
{
synchronized (LOCK) {
List<ClearTextMessage> l = new ArrayList<>();
l.addAll(getOmemoService().decryptMamQueryResult(this, mamQueryResult));
l.addAll(getOmemoService().decryptMamQueryResult(new KnownBareJidGuard(this), mamQueryResult));
return l;
}
}
/**
* Trust that a fingerprint belongs to an OmemoDevice.
@ -346,7 +422,11 @@ public final class OmemoManager extends Manager {
* @param fingerprint fingerprint
*/
public void trustOmemoIdentity(OmemoDevice device, OmemoFingerprint fingerprint) {
getOmemoService().getOmemoStoreBackend().trustOmemoIdentity(this, device, fingerprint);
if (trustCallback == null) {
throw new IllegalStateException("No TrustCallback set.");
}
trustCallback.setTrust(device, fingerprint, TrustState.trusted);
}
/**
@ -357,7 +437,11 @@ public final class OmemoManager extends Manager {
* @param fingerprint fingerprint
*/
public void distrustOmemoIdentity(OmemoDevice device, OmemoFingerprint fingerprint) {
getOmemoService().getOmemoStoreBackend().distrustOmemoIdentity(this, device, fingerprint);
if (trustCallback == null) {
throw new IllegalStateException("No TrustCallback set.");
}
trustCallback.setTrust(device, fingerprint, TrustState.untrusted);
}
/**
@ -369,7 +453,11 @@ public final class OmemoManager extends Manager {
* @return
*/
public boolean isTrustedOmemoIdentity(OmemoDevice device, OmemoFingerprint fingerprint) {
return getOmemoService().getOmemoStoreBackend().isTrustedOmemoIdentity(this, device, fingerprint);
if (trustCallback == null) {
throw new IllegalStateException("No TrustCallback set.");
}
return trustCallback.getTrust(device, fingerprint) == TrustState.trusted;
}
/**
@ -381,7 +469,11 @@ public final class OmemoManager extends Manager {
* @return
*/
public boolean isDecidedOmemoIdentity(OmemoDevice device, OmemoFingerprint fingerprint) {
return getOmemoService().getOmemoStoreBackend().isDecidedOmemoIdentity(this, device, fingerprint);
if (trustCallback == null) {
throw new IllegalStateException("No TrustCallback set.");
}
return trustCallback.getTrust(device, fingerprint) != TrustState.undecided;
}
/**
@ -392,9 +484,14 @@ public final class OmemoManager extends Manager {
* @throws XMPPException.XMPPErrorException
* @throws CorruptedOmemoKeyException
*/
public void purgeDevices() throws SmackException, InterruptedException, XMPPException.XMPPErrorException, CorruptedOmemoKeyException {
getOmemoService().publishDeviceIdIfNeeded(this,true);
getOmemoService().publishBundle(this);
public void purgeDeviceList()
throws SmackException, InterruptedException, XMPPException.XMPPErrorException, CorruptedOmemoKeyException
{
synchronized (LOCK) {
KnownBareJidGuard managerGuard = new KnownBareJidGuard(this);
getOmemoService().publishDeviceIdIfNeeded(managerGuard, true);
getOmemoService().publishBundle(managerGuard);
}
}
/**
@ -404,11 +501,16 @@ public final class OmemoManager extends Manager {
* @throws XMPPException.XMPPErrorException
* @throws CorruptedOmemoKeyException
*/
public void regenerate() throws SmackException, InterruptedException, XMPPException.XMPPErrorException, CorruptedOmemoKeyException {
public void regenerateIdentity()
throws SmackException, InterruptedException, XMPPException.XMPPErrorException, CorruptedOmemoKeyException
{
synchronized (LOCK) {
KnownBareJidGuard managerGuard = new KnownBareJidGuard(this);
// create a new identity and publish new keys to the server
getOmemoService().regenerate(this, null);
getOmemoService().publishDeviceIdIfNeeded(this,false);
getOmemoService().publishBundle(this);
getOmemoService().regenerate(managerGuard);
getOmemoService().publishDeviceIdIfNeeded(managerGuard, false);
getOmemoService().publishBundle(managerGuard);
}
}
/**
@ -423,8 +525,12 @@ public final class OmemoManager extends Manager {
*/
public void sendRatchetUpdateMessage(OmemoDevice recipient)
throws CorruptedOmemoKeyException, UndecidedOmemoIdentityException, CryptoFailedException,
CannotEstablishOmemoSessionException {
getOmemoService().sendOmemoRatchetUpdateMessage(this, recipient, false);
CannotEstablishOmemoSessionException, SmackException.NotLoggedInException
{
synchronized (LOCK) {
getOmemoService().sendOmemoRatchetUpdateMessage(
new KnownBareJidGuard(this), recipient, false);
}
}
/**
@ -442,8 +548,12 @@ public final class OmemoManager extends Manager {
*/
public OmemoVAxolotlElement createKeyTransportElement(byte[] aesKey, byte[] iv, OmemoDevice ... to)
throws UndecidedOmemoIdentityException, CorruptedOmemoKeyException, CryptoFailedException,
CannotEstablishOmemoSessionException {
return getOmemoService().prepareOmemoKeyTransportElement(this, aesKey, iv, to);
CannotEstablishOmemoSessionException, SmackException.NotLoggedInException
{
synchronized (LOCK) {
return getOmemoService().prepareOmemoKeyTransportElement(
new KnownBareJidGuard(this), aesKey, iv, to);
}
}
/**
@ -489,11 +599,18 @@ public final class OmemoManager extends Manager {
* @throws InterruptedException
* @throws SmackException.NoResponseException
*/
public boolean contactSupportsOmemo(BareJid contact) throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
getOmemoService().refreshDeviceList(this, contact);
return !getOmemoService().getOmemoStoreBackend().loadCachedDeviceList(this, contact)
public boolean contactSupportsOmemo(BareJid contact)
throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException,
SmackException.NotLoggedInException
{
synchronized (LOCK) {
KnownBareJidGuard managerGuard = new KnownBareJidGuard(this);
getOmemoService().refreshDeviceList(managerGuard, contact);
return !getOmemoService().getOmemoStoreBackend().loadCachedDeviceList(getOwnDevice(), contact)
.getActiveDevices().isEmpty();
}
}
/**
* Returns true, if the MUC with the EntityBareJid multiUserChat is non-anonymous and members only (prerequisite
@ -506,7 +623,10 @@ public final class OmemoManager extends Manager {
* @throws InterruptedException goes
* @throws SmackException.NoResponseException wrong
*/
public boolean multiUserChatSupportsOmemo(EntityBareJid multiUserChat) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
public boolean multiUserChatSupportsOmemo(EntityBareJid multiUserChat)
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException
{
RoomInfo roomInfo = MultiUserChatManager.getInstanceFor(connection()).getRoomInfo(multiUserChat);
return roomInfo.isNonanonymous() && roomInfo.isMembersOnly();
}
@ -522,8 +642,12 @@ public final class OmemoManager extends Manager {
* @throws InterruptedException
* @throws SmackException.NoResponseException
*/
public static boolean serverSupportsOmemo(XMPPConnection connection, DomainBareJid server) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
return ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(server).containsFeature(PubSub.NAMESPACE);
public static boolean serverSupportsOmemo(XMPPConnection connection, DomainBareJid server)
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException
{
return ServiceDiscoveryManager.getInstanceFor(connection)
.discoverInfo(server).containsFeature(PubSub.NAMESPACE);
}
/**
@ -531,16 +655,33 @@ public final class OmemoManager extends Manager {
*
* @return fingerprint
*/
public OmemoFingerprint getOurFingerprint() {
return getOmemoService().getOmemoStoreBackend().getFingerprint(this);
public OmemoFingerprint getOwnFingerprint()
throws SmackException.NotLoggedInException, CorruptedOmemoKeyException
{
synchronized (LOCK) {
if (getOwnJid() == null) {
throw new SmackException.NotLoggedInException();
}
return getOmemoService().getOmemoStoreBackend().getFingerprint(getOwnDevice());
}
}
public OmemoFingerprint getFingerprint(OmemoDevice device)
throws CannotEstablishOmemoSessionException, SmackException.NotLoggedInException,
CorruptedOmemoKeyException
{
synchronized (LOCK) {
if (getOwnJid() == null) {
throw new SmackException.NotLoggedInException();
}
public OmemoFingerprint getFingerprint(OmemoDevice device) throws CannotEstablishOmemoSessionException {
if (device.equals(getOwnDevice())) {
return getOurFingerprint();
return getOwnFingerprint();
}
return getOmemoService().getOmemoStoreBackend().getFingerprint(this, device);
return getOmemoService().getOmemoStoreBackend().getFingerprint(new KnownBareJidGuard(this), device);
}
}
/**
@ -548,25 +689,31 @@ public final class OmemoManager extends Manager {
* @param contact contact
* @return HashMap of deviceIds and corresponding fingerprints.
*/
public HashMap<OmemoDevice, OmemoFingerprint> getActiveFingerprints(BareJid contact) {
public HashMap<OmemoDevice, OmemoFingerprint> getActiveFingerprints(BareJid contact)
throws SmackException.NotLoggedInException, CorruptedOmemoKeyException,
CannotEstablishOmemoSessionException
{
synchronized (LOCK) {
if (getOwnJid() == null) {
throw new SmackException.NotLoggedInException();
}
HashMap<OmemoDevice, OmemoFingerprint> fingerprints = new HashMap<>();
CachedDeviceList deviceList = getOmemoService().getOmemoStoreBackend().loadCachedDeviceList(this, contact);
CachedDeviceList deviceList = getOmemoService().getOmemoStoreBackend()
.loadCachedDeviceList(getOwnDevice(), contact);
for (int id : deviceList.getActiveDevices()) {
OmemoDevice device = new OmemoDevice(contact, id);
OmemoFingerprint fingerprint = null;
try {
fingerprint = getFingerprint(device);
} catch (CannotEstablishOmemoSessionException e) {
LOGGER.log(Level.WARNING, "Could not build session with device " + id
+ " of user " + contact + ": " + e.getMessage());
}
OmemoFingerprint fingerprint = getFingerprint(device);
if (fingerprint != null) {
fingerprints.put(device, fingerprint);
}
}
return fingerprints;
}
}
public void addOmemoMessageListener(OmemoMessageListener listener) {
omemoMessageListeners.add(listener);
@ -593,8 +740,13 @@ public final class OmemoManager extends Manager {
* @throws SmackException.NotConnectedException
* @throws SmackException.NoResponseException
*/
public void buildSessionsWith(BareJid contact) throws InterruptedException, CannotEstablishOmemoSessionException, SmackException.NotConnectedException, SmackException.NoResponseException {
getOmemoService().buildOrCreateOmemoSessionsFromBundles(this, contact);
public void buildSessionsWith(BareJid contact)
throws InterruptedException, CannotEstablishOmemoSessionException, SmackException.NotConnectedException,
SmackException.NoResponseException, SmackException.NotLoggedInException
{
synchronized (LOCK) {
getOmemoService().buildMissingOmemoSessions(new KnownBareJidGuard(this), contact);
}
}
/**
@ -605,8 +757,13 @@ public final class OmemoManager extends Manager {
* @throws InterruptedException
* @throws SmackException.NoResponseException
*/
public void requestDeviceListUpdateFor(BareJid contact) throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
getOmemoService().refreshDeviceList(this, contact);
public void requestDeviceListUpdateFor(BareJid contact)
throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException,
SmackException.NotLoggedInException
{
synchronized (LOCK) {
getOmemoService().refreshDeviceList(new KnownBareJidGuard(this), contact);
}
}
/**
@ -621,12 +778,19 @@ public final class OmemoManager extends Manager {
* @throws SmackException.NoResponseException XMPP error
* @throws PubSubException.NotALeafNodeException if the bundle node on the server is a CollectionNode
*/
public void rotateSignedPreKey() throws CorruptedOmemoKeyException, InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException, PubSubException.NotALeafNodeException {
public void rotateSignedPreKey()
throws CorruptedOmemoKeyException, InterruptedException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, SmackException.NoResponseException,
PubSubException.NotALeafNodeException, SmackException.NotLoggedInException
{
synchronized (LOCK) {
KnownBareJidGuard managerGuard = new KnownBareJidGuard(this);
// generate key
getOmemoService().getOmemoStoreBackend().changeSignedPreKey(this);
getOmemoService().getOmemoStoreBackend().changeSignedPreKey(getOwnDevice());
// publish
getOmemoService().publishDeviceIdIfNeeded(this, false);
getOmemoService().publishBundle(this);
getOmemoService().publishDeviceIdIfNeeded(managerGuard, false);
getOmemoService().publishBundle(managerGuard);
}
}
/**
@ -647,14 +811,12 @@ public final class OmemoManager extends Manager {
}
}
/**
* Returns a pseudo random number from the interval [1, Integer.MAX_VALUE].
* @return deviceId
*/
public static int randomDeviceId() {
int i = new Random().nextInt(Integer.MAX_VALUE);
if (i == 0) {
return randomDeviceId();
}
return Math.abs(i);
return new Random().nextInt(Integer.MAX_VALUE - 1) + 1;
}
/**
@ -663,9 +825,11 @@ public final class OmemoManager extends Manager {
* @return bareJid
*/
public BareJid getOwnJid() {
EntityFullJid fullJid = connection().getUser();
if (fullJid == null) return null;
return fullJid.asBareJid();
if (ownJid == null && connection().isAuthenticated()) {
ownJid = connection().getUser().asBareJid();
}
return ownJid;
}
/**
@ -673,9 +837,11 @@ public final class OmemoManager extends Manager {
*
* @return deviceId
*/
public int getDeviceId() {
public Integer getDeviceId() {
synchronized (LOCK) {
return deviceId;
}
}
/**
* Return the OmemoDevice of the user.
@ -683,14 +849,24 @@ public final class OmemoManager extends Manager {
* @return omemoDevice
*/
public OmemoDevice getOwnDevice() {
return new OmemoDevice(getOwnJid(), getDeviceId());
synchronized (LOCK) {
BareJid jid = getOwnJid();
if (jid == null) {
return null;
}
return new OmemoDevice(jid, getDeviceId());
}
}
void setDeviceId(int nDeviceId) {
synchronized (LOCK) {
// Move this instance inside the HashMaps
INSTANCES.get(connection()).remove(getDeviceId());
INSTANCES.get(connection()).put(nDeviceId, this);
this.deviceId = nDeviceId;
}
}
/**
* Notify all registered OmemoMessageListeners about a received OmemoMessage.
@ -700,14 +876,21 @@ public final class OmemoManager extends Manager {
* @param wrappingMessage message that wrapped the incoming message
* @param messageInformation information about the messages encryption (used identityKey, carbon...)
*/
void notifyOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation messageInformation) {
void notifyOmemoMessageReceived(String decryptedBody,
Message encryptedMessage,
Message wrappingMessage,
OmemoMessageInformation messageInformation)
{
for (OmemoMessageListener l : omemoMessageListeners) {
l.onOmemoMessageReceived(decryptedBody, encryptedMessage, wrappingMessage, messageInformation);
}
}
void notifyOmemoKeyTransportMessageReceived(CipherAndAuthTag cipherAndAuthTag, Message transportingMessage,
Message wrappingMessage, OmemoMessageInformation information) {
void notifyOmemoKeyTransportMessageReceived(CipherAndAuthTag cipherAndAuthTag,
Message transportingMessage,
Message wrappingMessage,
OmemoMessageInformation information)
{
for (OmemoMessageListener l : omemoMessageListeners) {
l.onOmemoKeyTransportReceived(cipherAndAuthTag, transportingMessage, wrappingMessage, information);
}
@ -723,17 +906,26 @@ public final class OmemoManager extends Manager {
* @param wrappingMessage wrapping message (in case of carbon copy)
* @param omemoInformation information about the encryption of the message
*/
void notifyOmemoMucMessageReceived(MultiUserChat muc, BareJid from, String decryptedBody, Message message,
Message wrappingMessage, OmemoMessageInformation omemoInformation) {
void notifyOmemoMucMessageReceived(MultiUserChat muc,
BareJid from,
String decryptedBody,
Message message,
Message wrappingMessage,
OmemoMessageInformation omemoInformation)
{
for (OmemoMucMessageListener l : omemoMucMessageListeners) {
l.onOmemoMucMessageReceived(muc, from, decryptedBody, message,
wrappingMessage, omemoInformation);
}
}
void notifyOmemoMucKeyTransportMessageReceived(MultiUserChat muc, BareJid from, CipherAndAuthTag cipherAndAuthTag,
Message transportingMessage, Message wrappingMessage,
OmemoMessageInformation messageInformation) {
void notifyOmemoMucKeyTransportMessageReceived(MultiUserChat muc,
BareJid from,
CipherAndAuthTag cipherAndAuthTag,
Message transportingMessage,
Message wrappingMessage,
OmemoMessageInformation messageInformation)
{
for (OmemoMucMessageListener l : omemoMucMessageListeners) {
l.onOmemoKeyTransportReceived(muc, from, cipherAndAuthTag,
transportingMessage, wrappingMessage, messageInformation);
@ -741,13 +933,28 @@ public final class OmemoManager extends Manager {
}
/**
* Remove all active stanza listeners of this manager from the connection.
* This is somewhat the counterpart of initialize().
* Register stanza listeners needed for OMEMO.
* This method is called automatically in the constructor and should only be used to restore the previous state
* after {@link #stopListeners()} was called.
*/
public void shutdown() {
public void startStanzaListeners() {
PEPManager pepManager = PEPManager.getInstanceFor(connection());
pepManager.removePEPListener(deviceListUpdateListener);
pepManager.addPEPListener(deviceListUpdateListener);
connection().removeAsyncStanzaListener(internalOmemoMessageStanzaListener);
connection().addAsyncStanzaListener(internalOmemoMessageStanzaListener, omemoMessageStanzaFilter);
CarbonManager carbonManager = CarbonManager.getInstanceFor(connection());
carbonManager.removeCarbonCopyReceivedListener(internalOmemoCarbonCopyListener);
carbonManager.addCarbonCopyReceivedListener(internalOmemoCarbonCopyListener);
}
/**
* Remove active stanza listeners needed for OMEMO.
*/
public void stopListeners() {
PEPManager.getInstanceFor(connection()).removePEPListener(deviceListUpdateListener);
connection().removeAsyncStanzaListener(omemoStanzaListener);
CarbonManager.getInstanceFor(connection()).removeCarbonCopyReceivedListener(omemoCarbonCopyListener);
connection().removeAsyncStanzaListener(internalOmemoMessageStanzaListener);
CarbonManager.getInstanceFor(connection()).removeCarbonCopyReceivedListener(internalOmemoCarbonCopyListener);
}
/**
@ -769,9 +976,21 @@ public final class OmemoManager extends Manager {
return service;
}
PEPListener deviceListUpdateListener = new PEPListener() {
private final PEPListener deviceListUpdateListener = new PEPListener() {
KnownBareJidGuard managerGuard = null;
@Override
public void eventReceived(EntityBareJid from, EventElement event, Message message) {
if (managerGuard == null) {
try {
managerGuard = new KnownBareJidGuard(OmemoManager.this);
} catch (SmackException.NotLoggedInException e) {
throw new AssertionError(e);
}
}
for (ExtensionElement items : event.getExtensions()) {
if (!(items instanceof ItemsExtension)) {
continue;
@ -789,9 +1008,12 @@ public final class OmemoManager extends Manager {
}
// Device List <list>
OmemoDeviceListVAxolotlElement omemoDeviceListElement = (OmemoDeviceListVAxolotlElement) payloadItem.getPayload();
OmemoDeviceListVAxolotlElement omemoDeviceListElement =
(OmemoDeviceListVAxolotlElement) payloadItem.getPayload();
int ourDeviceId = getDeviceId();
getOmemoService().getOmemoStoreBackend().mergeCachedDeviceList(OmemoManager.this, from, omemoDeviceListElement);
getOmemoService().getOmemoStoreBackend()
.mergeCachedDeviceList(managerGuard.get().getOwnDevice(), from, omemoDeviceListElement);
if (from == null) {
// Unknown sender, no more work to do.
@ -812,22 +1034,24 @@ public final class OmemoManager extends Manager {
// Our deviceList and we are not on it! We don't want to miss all the action!!!
LOGGER.log(Level.INFO, "Our deviceId was not on the list!");
Set<Integer> deviceListIds = omemoDeviceListElement.copyDeviceIds();
// enroll at the deviceList
deviceListIds.add(ourDeviceId);
final OmemoDeviceListVAxolotlElement newOmemoDeviceListElement = new OmemoDeviceListVAxolotlElement(deviceListIds);
final OmemoDeviceListVAxolotlElement newOmemoDeviceListElement =
new OmemoDeviceListVAxolotlElement(deviceListIds);
// PEPListener is a synchronous listener. Avoid any deadlocks by using an async task to update the device list.
// PEPListener is a synchronous listener.
// Avoid any deadlocks by using an async task to update the device list.
Async.go(new Runnable() {
@Override
public void run() {
try {
OmemoService.publishDeviceIds(OmemoManager.this, newOmemoDeviceListElement);
OmemoService.publishDeviceIds(managerGuard, newOmemoDeviceListElement);
}
catch (SmackException | InterruptedException | XMPPException.XMPPErrorException e) {
// TODO: It might be dangerous NOT to retry publishing our deviceId
LOGGER.log(Level.SEVERE,
"Could not publish our device list after an update without our id was received: "
+ e.getMessage());
LOGGER.log(Level.SEVERE, "Could not publish our device list after an update " +
"without our id was received: " + e.getMessage());
}
}
});
@ -836,19 +1060,75 @@ public final class OmemoManager extends Manager {
}
};
OmemoService<?,?,?,?,?,?,?,?,?>.OmemoStanzaListener getOmemoStanzaListener() {
if (omemoStanzaListener == null) {
omemoStanzaListener = getOmemoService().createStanzaListener(this);
private final StanzaListener internalOmemoMessageStanzaListener = new StanzaListener() {
@Override
public void processStanza(Stanza packet) throws SmackException.NotConnectedException, InterruptedException {
try {
getOmemoService().onOmemoMessageStanzaReceived(packet,
new KnownBareJidGuard(OmemoManager.this));
} catch (SmackException.NotLoggedInException e) {
LOGGER.warning("Received OMEMO stanza while being offline: " + e);
}
return omemoStanzaListener;
}
};
private final CarbonCopyReceivedListener internalOmemoCarbonCopyListener = new CarbonCopyReceivedListener() {
@Override
public void onCarbonCopyReceived(CarbonExtension.Direction direction,
Message carbonCopy,
Message wrappingMessage) {
if (omemoMessageStanzaFilter.accept(carbonCopy)) {
try {
getOmemoService().onOmemoCarbonCopyReceived(direction, carbonCopy, wrappingMessage,
new KnownBareJidGuard(OmemoManager.this));
} catch (SmackException.NotLoggedInException e) {
LOGGER.warning("Received OMEMO carbon copy while being offline: " + e);
}
}
}
};
/**
* StanzaFilter that filters messages containing a OMEMO element.
*/
private final StanzaFilter omemoMessageStanzaFilter = new StanzaFilter() {
@Override
public boolean accept(Stanza stanza) {
return stanza instanceof Message && OmemoManager.stanzaContainsOmemoElement(stanza);
}
};
public static class KnownBareJidGuard {
private final OmemoManager manager;
public KnownBareJidGuard(OmemoManager manager)
throws SmackException.NotLoggedInException {
if (manager == null) {
throw new IllegalArgumentException("OmemoManager cannot be null.");
}
OmemoService<?,?,?,?,?,?,?,?,?>.OmemoCarbonCopyListener getOmemoCarbonCopyListener() {
if (omemoCarbonCopyListener == null) {
omemoCarbonCopyListener = getOmemoService().createOmemoCarbonCopyListener(this);
}
return omemoCarbonCopyListener;
if (manager.getOwnJid() == null) {
if (manager.getConnection().isAuthenticated()) {
manager.ownJid = manager.getConnection().getUser().asBareJid();
} else {
throw new SmackException.NotLoggedInException();
}
}
this.manager = manager;
}
public OmemoManager get() {
return manager;
}
}
public interface FinishedCallback {
void initializationFinished(OmemoManager manager);
void initializationFailed(Exception cause);
}
}

View file

@ -0,0 +1,152 @@
/**
*
* 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 java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.omemo.element.OmemoElement;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
import org.jivesoftware.smackx.omemo.exceptions.MultipleCryptoFailedException;
import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException;
import org.jivesoftware.smackx.omemo.exceptions.UntrustedOmemoIdentityException;
import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
import org.jivesoftware.smackx.omemo.internal.CiphertextTuple;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
public abstract class OmemoSessionManager<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> {
private static final Logger LOGGER = Logger.getLogger(OmemoSessionManager.class.getName());
protected final OmemoManager.KnownBareJidGuard managerGuard;
protected final OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> store;
public OmemoSessionManager(OmemoManager.KnownBareJidGuard managerGuard,
OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> store) {
this.managerGuard = managerGuard;
this.store = store;
}
public abstract byte[] doubleRatchetDecrypt(OmemoDevice sender, byte[] encryptedKey)
throws CorruptedOmemoKeyException, NoRawSessionException, CryptoFailedException,
UntrustedOmemoIdentityException;
public abstract CiphertextTuple doubleRatchetEncrypt(OmemoDevice recipient, byte[] messageKey);
/**
* Try to decrypt the transported message key using the double ratchet session.
*
* @param element omemoElement
* @return tuple of cipher generated from the unpacked message key and the authtag
* @throws CryptoFailedException if decryption using the double ratchet fails
* @throws NoRawSessionException if we have no session, but the element was NOT a PreKeyMessage
*/
public CipherAndAuthTag retrieveMessageKeyAndAuthTag(OmemoDevice sender, OmemoElement element) throws CryptoFailedException,
NoRawSessionException {
int keyId = managerGuard.get().getDeviceId();
byte[] unpackedKey = null;
List<CryptoFailedException> decryptExceptions = new ArrayList<>();
List<OmemoElement.OmemoHeader.Key> keys = element.getHeader().getKeys();
// Find key with our ID.
for (OmemoElement.OmemoHeader.Key k : keys) {
if (k.getId() == keyId) {
try {
unpackedKey = doubleRatchetDecrypt(sender, k.getData());
break;
} catch (CryptoFailedException e) {
// There might be multiple keys with our id, but we can only decrypt one.
// So we can't throw the exception, when decrypting the first duplicate which is not for us.
decryptExceptions.add(e);
} catch (CorruptedOmemoKeyException e) {
decryptExceptions.add(new CryptoFailedException(e));
} catch (UntrustedOmemoIdentityException e) {
LOGGER.log(Level.WARNING, "Received message from " + sender + " contained unknown identityKey. Ignore message.", e);
}
}
}
if (unpackedKey == null) {
if (!decryptExceptions.isEmpty()) {
throw MultipleCryptoFailedException.from(decryptExceptions);
}
throw new CryptoFailedException("Transported key could not be decrypted, since no suitable message key " +
"was provided. Provides keys: " + keys);
}
// Split in AES auth-tag and key
byte[] messageKey = new byte[16];
byte[] authTag = null;
if (unpackedKey.length == 32) {
authTag = new byte[16];
// copy key part into messageKey
System.arraycopy(unpackedKey, 0, messageKey, 0, 16);
// copy tag part into authTag
System.arraycopy(unpackedKey, 16, authTag, 0,16);
} else if (element.isKeyTransportElement() && unpackedKey.length == 16) {
messageKey = unpackedKey;
} else {
throw new CryptoFailedException("MessageKey has wrong length: "
+ unpackedKey.length + ". Probably legacy auth tag format.");
}
return new CipherAndAuthTag(messageKey, element.getHeader().getIv(), authTag);
}
/**
* Use the symmetric key in cipherAndAuthTag to decrypt the payload of the omemoMessage.
* The decrypted payload will be the body of the returned Message.
*
* @param element omemoElement containing a payload.
* @param cipherAndAuthTag cipher and authentication tag.
* @return Message containing the decrypted payload in its body.
* @throws CryptoFailedException
*/
public static Message decryptMessageElement(OmemoElement element, CipherAndAuthTag cipherAndAuthTag) throws CryptoFailedException {
if (!element.isMessageElement()) {
throw new IllegalArgumentException("decryptMessageElement cannot decrypt OmemoElement which is no MessageElement!");
}
if (cipherAndAuthTag.getAuthTag() == null || cipherAndAuthTag.getAuthTag().length != 16) {
throw new CryptoFailedException("AuthenticationTag is null or has wrong length: "
+ (cipherAndAuthTag.getAuthTag() == null ? "null" : cipherAndAuthTag.getAuthTag().length));
}
byte[] encryptedBody = new byte[element.getPayload().length + 16];
byte[] payload = element.getPayload();
System.arraycopy(payload, 0, encryptedBody, 0, payload.length);
System.arraycopy(cipherAndAuthTag.getAuthTag(), 0, encryptedBody, payload.length, 16);
try {
String plaintext = new String(cipherAndAuthTag.getCipher().doFinal(encryptedBody), StringUtils.UTF8);
Message decrypted = new Message();
decrypted.setBody(plaintext);
return decrypted;
} catch (UnsupportedEncodingException | IllegalBlockSizeException | BadPaddingException e) {
throw new CryptoFailedException("decryptMessageElement could not decipher message body: "
+ e.getMessage());
}
}
}

View file

@ -21,20 +21,17 @@ import static org.jivesoftware.smackx.omemo.util.OmemoConstants.TARGET_PRE_KEY_C
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.roster.Roster;
import org.jivesoftware.smack.roster.RosterEntry;
import org.jivesoftware.smackx.omemo.element.OmemoBundleVAxolotlElement;
import org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement;
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.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.internal.OmemoSession;
import org.jivesoftware.smackx.omemo.util.OmemoKeyUtil;
import org.jxmpp.jid.BareJid;
@ -56,9 +53,6 @@ import org.jxmpp.jid.BareJid;
public abstract class OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> {
private static final Logger LOGGER = Logger.getLogger(OmemoStore.class.getName());
private final WeakHashMap<OmemoManager, HashMap<OmemoDevice, OmemoSession<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>>>
omemoSessions = new WeakHashMap<>();
/**
* Create a new OmemoStore.
*/
@ -67,26 +61,28 @@ public abstract class OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
}
/**
* Return true if this is a fresh installation.
*
* @param omemoManager omemoManager of our device.
* @return true or false.
* Returns a sorted set of all the deviceIds, the localUser has had data stored under in the store.
* Basically this returns the deviceIds of all "accounts" of localUser, which are known to the store.
* @param localUser BareJid of the user.
* @return set of deviceIds with available data.
*/
public abstract boolean isFreshInstallation(OmemoManager omemoManager);
public abstract SortedSet<Integer> localDeviceIdsOf(BareJid localUser);
/**
* Check, if our freshly generated deviceId is available (unique) in our deviceList.
*
* @param omemoManager omemoManager of our device.
* @param id our deviceId.
* @param userDevice our current device.
* @param id deviceId to check for.
* @return true if list did not contain our id, else false
*/
boolean isAvailableDeviceId(OmemoManager omemoManager, int id) {
boolean isAvailableDeviceId(OmemoDevice userDevice, int id) {
LOGGER.log(Level.INFO, "Check if id " + id + " is available...");
// Lookup local cached device list
BareJid ownJid = omemoManager.getOwnJid();
CachedDeviceList cachedDeviceList = loadCachedDeviceList(omemoManager, ownJid);
BareJid ownJid = userDevice.getJid();
CachedDeviceList cachedDeviceList;
cachedDeviceList = loadCachedDeviceList(userDevice, ownJid);
if (cachedDeviceList == null) {
cachedDeviceList = new CachedDeviceList();
@ -95,32 +91,15 @@ public abstract class OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
return !cachedDeviceList.contains(id);
}
/**
* Generate a new Identity (deviceId, identityKeys, preKeys...).
*
* @param omemoManager omemoManager of our device we want to regenerate.
* @throws CorruptedOmemoKeyException in case something goes wrong
*/
void regenerate(OmemoManager omemoManager) throws CorruptedOmemoKeyException {
LOGGER.log(Level.INFO, "Regenerating with deviceId " + omemoManager.getDeviceId() + "...");
int nextPreKeyId = 1;
storeOmemoIdentityKeyPair(omemoManager, generateOmemoIdentityKeyPair());
storeOmemoPreKeys(omemoManager, generateOmemoPreKeys(nextPreKeyId, TARGET_PRE_KEY_COUNT));
storeLastPreKeyId(omemoManager, OmemoKeyUtil.addInBounds(nextPreKeyId, TARGET_PRE_KEY_COUNT));
storeCurrentSignedPreKeyId(omemoManager, -1); //Set back to no-value default
changeSignedPreKey(omemoManager);
initializeOmemoSessions(omemoManager);
}
/**
* Merge the received OmemoDeviceListElement with the one we already have. If we had none, the received one is saved.
*
* @param omemoManager omemoManager of our device.
* @param userDevice our OmemoDevice.
* @param contact Contact we received the list from.
* @param list List we received.
*/
void mergeCachedDeviceList(OmemoManager omemoManager, BareJid contact, OmemoDeviceListElement list) {
CachedDeviceList cached = loadCachedDeviceList(omemoManager, contact);
void mergeCachedDeviceList(OmemoDevice userDevice, BareJid contact, OmemoDeviceListElement list) {
CachedDeviceList cached = loadCachedDeviceList(userDevice, contact);
if (cached == null) {
cached = new CachedDeviceList();
@ -129,51 +108,56 @@ public abstract class OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
if (list != null) {
cached.merge(list.getDeviceIds());
}
storeCachedDeviceList(omemoManager, contact, cached);
storeCachedDeviceList(userDevice, contact, cached);
}
/**
* Renew our singed preKey. This should be done once every 7-14 days.
* The old signed PreKey should be kept for around a month or so (look it up in the XEP).
*
* @param omemoManager omemoManager of our device.
* @throws CorruptedOmemoKeyException when our identityKey is invalid
* @param userDevice our OmemoDevice.
* @throws CorruptedOmemoKeyException when our identityKey is invalid.
* @throws IllegalStateException when our IdentityKeyPair is null.
*/
void changeSignedPreKey(OmemoManager omemoManager) throws CorruptedOmemoKeyException {
int lastSignedPreKeyId = loadCurrentSignedPreKeyId(omemoManager);
if (lastSignedPreKeyId == -1) lastSignedPreKeyId = 0;
try {
T_SigPreKey newSignedPreKey = generateOmemoSignedPreKey(loadOmemoIdentityKeyPair(omemoManager), lastSignedPreKeyId + 1);
storeOmemoSignedPreKey(omemoManager, lastSignedPreKeyId + 1, newSignedPreKey);
storeCurrentSignedPreKeyId(omemoManager, lastSignedPreKeyId + 1);
setDateOfLastSignedPreKeyRenewal(omemoManager);
removeOldSignedPreKeys(omemoManager);
} catch (CorruptedOmemoKeyException e) {
LOGGER.log(Level.INFO, "Couldn't change SignedPreKey: " + e.getMessage());
throw e;
void changeSignedPreKey(OmemoDevice userDevice)
throws CorruptedOmemoKeyException
{
T_IdKeyPair idKeyPair = loadOmemoIdentityKeyPair(userDevice);
if (idKeyPair == null) {
throw new IllegalStateException("Our IdentityKeyPair is null.");
}
TreeMap<Integer, T_SigPreKey> signedPreKeys = loadOmemoSignedPreKeys(userDevice);
if (signedPreKeys.size() == 0) {
T_SigPreKey newKey = generateOmemoSignedPreKey(idKeyPair, 1);
storeOmemoSignedPreKey(userDevice, 1, newKey);
} else {
int lastId = signedPreKeys.lastKey();
T_SigPreKey newKey = generateOmemoSignedPreKey(idKeyPair, lastId + 1);
storeOmemoSignedPreKey(userDevice, lastId + 1, newKey);
}
setDateOfLastSignedPreKeyRenewal(userDevice, new Date());
removeOldSignedPreKeys(userDevice);
}
/**
* Remove the oldest signedPreKey until there are only MAX_NUMBER_OF_STORED_SIGNED_PREKEYS left.
*
* @param omemoManager omemoManager of our device.
* @param userDevice our OmemoDevice.
*/
private void removeOldSignedPreKeys(OmemoManager omemoManager) {
private void removeOldSignedPreKeys(OmemoDevice userDevice) {
if (OmemoConfiguration.getMaxNumberOfStoredSignedPreKeys() <= 0) {
return;
}
int currentId = loadCurrentSignedPreKeyId(omemoManager);
if (currentId == -1) currentId = 0;
HashMap<Integer, T_SigPreKey> signedPreKeys = loadOmemoSignedPreKeys(omemoManager);
TreeMap<Integer, T_SigPreKey> signedPreKeys = loadOmemoSignedPreKeys(userDevice);
for (int i : signedPreKeys.keySet()) {
if (i <= currentId - OmemoConfiguration.getMaxNumberOfStoredSignedPreKeys()) {
LOGGER.log(Level.INFO, "Remove signedPreKey " + i + ".");
removeOmemoSignedPreKey(omemoManager, i);
}
for (int i = 0; i < signedPreKeys.keySet().size() - OmemoConfiguration.getMaxNumberOfStoredSignedPreKeys(); i++) {
int keyId = signedPreKeys.firstKey();
LOGGER.log(Level.INFO, "Remove signedPreKey " + keyId + ".");
removeOmemoSignedPreKey(userDevice, i);
signedPreKeys = loadOmemoSignedPreKeys(userDevice);
}
}
@ -182,26 +166,36 @@ public abstract class OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
* If we used up n preKeys since we last published our bundle, generate n new preKeys and add them to the bundle.
* We should always publish TARGET_PRE_KEY_COUNT keys.
*
* @param omemoManager omemoManager of our device.
* @param userDevice our OmemoDevice.
* @return OmemoBundleElement
* @throws CorruptedOmemoKeyException when a key could not be loaded
*/
OmemoBundleVAxolotlElement packOmemoBundle(OmemoManager omemoManager) throws CorruptedOmemoKeyException {
int currentSignedPreKeyId = loadCurrentSignedPreKeyId(omemoManager);
if (currentSignedPreKeyId == -1) currentSignedPreKeyId = 0;
T_SigPreKey currentSignedPreKey = loadOmemoSignedPreKey(omemoManager, currentSignedPreKeyId);
T_IdKeyPair identityKeyPair = loadOmemoIdentityKeyPair(omemoManager);
OmemoBundleVAxolotlElement packOmemoBundle(OmemoDevice userDevice)
throws CorruptedOmemoKeyException
{
T_IdKeyPair identityKeyPair = loadOmemoIdentityKeyPair(userDevice);
if (identityKeyPair == null) {
identityKeyPair = generateOmemoIdentityKeyPair();
storeOmemoIdentityKeyPair(userDevice, identityKeyPair);
}
HashMap<Integer, T_PreKey> preKeys = loadOmemoPreKeys(omemoManager);
TreeMap<Integer, T_SigPreKey> signedPreKeys = loadOmemoSignedPreKeys(userDevice);
if (signedPreKeys.size() == 0) {
changeSignedPreKey(userDevice);
signedPreKeys = loadOmemoSignedPreKeys(userDevice);
}
int currentSignedPreKeyId = signedPreKeys.lastKey();
T_SigPreKey currentSignedPreKey = signedPreKeys.get(currentSignedPreKeyId);
TreeMap<Integer, T_PreKey> preKeys = loadOmemoPreKeys(userDevice);
int newKeysCount = TARGET_PRE_KEY_COUNT - preKeys.size();
int startId = preKeys.size() == 0 ? 0 : preKeys.lastKey();
if (newKeysCount > 0) {
int lastPreKeyId = loadLastPreKeyId(omemoManager);
if (lastPreKeyId == -1) lastPreKeyId = 0;
HashMap<Integer, T_PreKey> newKeys = generateOmemoPreKeys(lastPreKeyId + 1, newKeysCount);
storeOmemoPreKeys(omemoManager, newKeys);
TreeMap<Integer, T_PreKey> newKeys = generateOmemoPreKeys(startId + 1, newKeysCount);
storeOmemoPreKeys(userDevice, newKeys);
preKeys.putAll(newKeys);
storeLastPreKeyId(omemoManager, lastPreKeyId + newKeysCount);
}
return new OmemoBundleVAxolotlElement(
@ -213,159 +207,8 @@ public abstract class OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
);
}
/**
* Preload all OMEMO sessions for our devices and our contacts from existing raw sessions.
*
* @param omemoManager omemoManager of our device.
*/
void initializeOmemoSessions(OmemoManager omemoManager) {
// Get HashMap of our omemoSessions
HashMap<OmemoDevice, OmemoSession<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>>
sessions = omemoSessions.get(omemoManager);
if (sessions == null) {
sessions = new HashMap<>();
omemoSessions.put(omemoManager, sessions);
}
// Sessions with our own devices
HashMap<Integer, T_Sess> ourRawSessions = loadAllRawSessionsOf(omemoManager, omemoManager.getOwnJid());
ourRawSessions.remove(omemoManager.getDeviceId()); //Just to make sure we have no session with ourselves...
sessions.putAll(createOmemoSessionsFromRawSessions(omemoManager, omemoManager.getOwnJid(), ourRawSessions));
// Sessions with contacts
for (RosterEntry rosterEntry : Roster.getInstanceFor(omemoManager.getConnection()).getEntries()) {
HashMap<Integer, T_Sess> contactDevices = loadAllRawSessionsOf(omemoManager, rosterEntry.getJid().asBareJid());
sessions.putAll(createOmemoSessionsFromRawSessions(omemoManager, rosterEntry.getJid().asBareJid(), contactDevices));
}
}
/**
* Forget all omemoSessions of the omemoManager from cache.
* This will not remove the sessions from persistent memory!
*
* @param omemoManager omemoManager we want to forget sessions from.
*/
void forgetOmemoSessions(OmemoManager omemoManager) {
omemoSessions.remove(omemoManager);
}
/**
* Create a new concrete OmemoSession with a contact.
*
* @param omemoManager omemoManager of our device.
* @param device device to establish the session with
* @param identityKey identityKey of the device
* @return concrete OmemoSession
*/
private OmemoSession<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
createOmemoSession(OmemoManager omemoManager, OmemoDevice device, T_IdKey identityKey) {
return keyUtil().createOmemoSession(omemoManager, this, device, identityKey);
}
/**
* Return the OmemoSession for the OmemoDevice. If there is no OmemoSession for the device yet,
* build one from local raw session material.
*
* @param omemoManager omemoManager of our device.
* @param device OmemoDevice
* @return OmemoSession
*/
public OmemoSession<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
getOmemoSessionOf(OmemoManager omemoManager, OmemoDevice device) {
HashMap<OmemoDevice, OmemoSession<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>>
sessions = omemoSessions.get(omemoManager);
if (sessions == null) {
sessions = new HashMap<>();
omemoSessions.put(omemoManager, sessions);
}
OmemoSession<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
session = sessions.get(device);
// No OmemoSession found
if (session == null) {
T_IdKey identityKey = null;
try {
identityKey = loadOmemoIdentityKey(omemoManager, device);
} catch (CorruptedOmemoKeyException e) {
LOGGER.log(Level.WARNING, "getOmemoSessionOf could not load identityKey of " + device + ": " + e.getMessage());
}
if (identityKey != null) {
session = createOmemoSession(omemoManager, device, identityKey);
} else {
LOGGER.log(Level.INFO, "getOmemoSessionOf couldn't find an identityKey for " + device
+ ". Initiate session without.");
session = createOmemoSession(omemoManager, device, null);
}
sessions.put(device, session);
}
if (session.getIdentityKey() == null) {
try {
session.setIdentityKey(loadOmemoIdentityKey(omemoManager, device));
} catch (CorruptedOmemoKeyException e) {
LOGGER.log(Level.WARNING, "Can't update IdentityKey of " + device + ": " + e.getMessage());
}
}
return session;
}
/**
* Create OmemoSession objects for all T_Sess objects of the contact.
* The T_Sess objects will be wrapped inside a OmemoSession for every device of the contact.
*
* @param omemoManager omemoManager of our device.
* @param contact BareJid of the contact
* @param rawSessions HashMap of Integers (deviceIds) and T_Sess sessions.
* @return HashMap of OmemoContacts and OmemoSessions
*/
private HashMap<OmemoDevice, OmemoSession<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>>
createOmemoSessionsFromRawSessions(OmemoManager omemoManager, BareJid contact, HashMap<Integer, T_Sess> rawSessions) {
HashMap<OmemoDevice, OmemoSession<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>>
sessions = new HashMap<>();
for (Map.Entry<Integer, T_Sess> sessionEntry : rawSessions.entrySet()) {
OmemoDevice omemoDevice = new OmemoDevice(contact, sessionEntry.getKey());
try {
T_IdKey identityKey = loadOmemoIdentityKey(omemoManager, omemoDevice);
if (identityKey != null) {
sessions.put(omemoDevice, createOmemoSession(omemoManager, omemoDevice, identityKey));
} else {
LOGGER.log(Level.WARNING, "IdentityKey of " + omemoDevice + " is null. Is this even possible at this point?");
}
} catch (CorruptedOmemoKeyException e1) {
LOGGER.log(Level.WARNING, "buildOmemoSessionFor could not create a session for " + omemoDevice +
": " + e1.getMessage());
}
}
return sessions;
}
// *sigh*
/**
* Return the id of the last generated preKey.
* This is used to generate new preKeys without preKeyId collisions.
*
* @param omemoManager omemoManager of our device.
* @return id of the last preKey
*/
public abstract int loadLastPreKeyId(OmemoManager omemoManager);
/**
* Store the id of the last preKey we generated.
*
* @param omemoManager omemoManager of our device.
* @param currentPreKeyId the id of the last generated PreKey
*/
public abstract void storeLastPreKeyId(OmemoManager omemoManager, int currentPreKeyId);
/**
* Generate a new IdentityKeyPair. We should always have only one pair and usually keep this for a long time.
*
@ -378,150 +221,89 @@ public abstract class OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
/**
* Load our identityKeyPair from storage.
*
* @param omemoManager omemoManager of our device.
* @param userDevice our OmemoDevice.
* @return identityKeyPair
* @throws CorruptedOmemoKeyException Thrown, if the stored key is damaged (*hands up* not my fault!)
*/
public abstract T_IdKeyPair loadOmemoIdentityKeyPair(OmemoManager omemoManager) throws CorruptedOmemoKeyException;
public abstract T_IdKeyPair loadOmemoIdentityKeyPair(OmemoDevice userDevice)
throws CorruptedOmemoKeyException;
/**
* Store our identityKeyPair in storage. It would be a cool feature, if the key could be stored in a encrypted
* database or something similar.
*
* @param omemoManager omemoManager of our device.
* @param userDevice our OmemoDevice.
* @param identityKeyPair identityKeyPair
*/
public abstract void storeOmemoIdentityKeyPair(OmemoManager omemoManager, T_IdKeyPair identityKeyPair);
public abstract void storeOmemoIdentityKeyPair(OmemoDevice userDevice, T_IdKeyPair identityKeyPair);
/**
* Load the public identityKey of the device.
* Remove the identityKeyPair of a user.
* @param userDevice our device.
*/
public abstract void removeOmemoIdentityKeyPair(OmemoDevice userDevice);
/**
* Load the public identityKey of a device.
*
* @param omemoManager omemoManager of our device.
* @param device device
* @param userDevice our OmemoDevice.
* @param contactsDevice the device of which we want to load the identityKey.
* @return identityKey
* @throws CorruptedOmemoKeyException when the key in question is corrupted and cant be deserialized.
*/
public abstract T_IdKey loadOmemoIdentityKey(OmemoManager omemoManager, OmemoDevice device) throws CorruptedOmemoKeyException;
public abstract T_IdKey loadOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice)
throws CorruptedOmemoKeyException;
/**
* Store the public identityKey of the device.
*
* @param omemoManager omemoManager of our device.
* @param device device
* @param key identityKey
* @param userDevice our OmemoDevice.
* @param contactsDevice device.
* @param contactsKey identityKey belonging to the contactsDevice.
*/
public abstract void storeOmemoIdentityKey(OmemoManager omemoManager, OmemoDevice device, T_IdKey key);
public abstract void storeOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice, T_IdKey contactsKey);
/**
* Decide, whether a identityKey of a device is trusted or not.
* If you want to use this module, you should memorize, whether the user has trusted this key or not, since
* the owner of the identityKey will be able to read sent messages when this method returned 'true' for their
* identityKey. Either you let the user decide whether you trust a key every time you see a new key, or you
* implement something like 'blind trust' (see https://gultsch.de/trust.html).
* Removes the identityKey of a device.
*
* @param omemoManager omemoManager of our device.
* @param device Owner of the key
* @param identityKey identityKey
* @return true, if the user trusts the key and wants to send messages to it, otherwise false
* @param userDevice our omemoDevice.
* @param contactsDevice device of which we want to delete the identityKey.
*/
public boolean isTrustedOmemoIdentity(OmemoManager omemoManager, OmemoDevice device, T_IdKey identityKey) {
return isTrustedOmemoIdentity(omemoManager, device, keyUtil().getFingerprint(identityKey));
}
public abstract boolean isTrustedOmemoIdentity(OmemoManager omemoManager, OmemoDevice device, OmemoFingerprint fingerprint);
public abstract void removeOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice);
/**
* Did the user yet made a decision about whether to trust or distrust this device?
* Set the date in millis of the last message that was received from a device.
*
* @param omemoManager omemoManager of our device.
* @param device device
* @param identityKey IdentityKey
* @return true, if the user either trusted or distrusted the device. Return false, if the user did not yet decide.
*/
public boolean isDecidedOmemoIdentity(OmemoManager omemoManager, OmemoDevice device, T_IdKey identityKey) {
return isDecidedOmemoIdentity(omemoManager, device, keyUtil().getFingerprint(identityKey));
}
public abstract boolean isDecidedOmemoIdentity(OmemoManager omemoManager, OmemoDevice device, OmemoFingerprint fingerprint);
/**
* Trust an OmemoIdentity. This involves marking the key as trusted.
*
* @param omemoManager omemoManager of our device.
* @param device device
* @param identityKey identityKey
*/
public void trustOmemoIdentity(OmemoManager omemoManager, OmemoDevice device, T_IdKey identityKey) {
trustOmemoIdentity(omemoManager, device, keyUtil().getFingerprint(identityKey));
}
public abstract void trustOmemoIdentity(OmemoManager omemoManager, OmemoDevice device, OmemoFingerprint identityKeyFingerprint);
/**
* Distrust an OmemoIdentity. This involved marking the key as distrusted.
*
* @param omemoManager omemoManager of our device.
* @param device device
* @param identityKey identityKey
*/
public void distrustOmemoIdentity(OmemoManager omemoManager, OmemoDevice device, T_IdKey identityKey) {
distrustOmemoIdentity(omemoManager, device, keyUtil().getFingerprint(identityKey));
}
public abstract void distrustOmemoIdentity(OmemoManager omemoManager, OmemoDevice device, OmemoFingerprint fingerprint);
/**
* Set the date in millis of the last message that was received from device 'from' to 'date'.
*
* @param omemoManager omemoManager of our device.
* @param from device in question
* @param userDevice omemoManager of our device.
* @param contactsDevice device in question
* @param date date of the last received message
*/
public abstract void setDateOfLastReceivedMessage(OmemoManager omemoManager, OmemoDevice from, Date date);
/**
* Set the date in millis of the last message that was received from device 'from' to now.
*
* @param omemoManager omemoManager of our device.
* @param from device in question
*/
public void setDateOfLastReceivedMessage(OmemoManager omemoManager, OmemoDevice from) {
this.setDateOfLastReceivedMessage(omemoManager, from, new Date());
}
public abstract void setDateOfLastReceivedMessage(OmemoDevice userDevice, OmemoDevice contactsDevice, Date date);
/**
* Return the date in millis of the last message that was received from device 'from'.
*
* @param omemoManager omemoManager of our device.
* @param from device in question
* @param userDevice our OmemoDevice.
* @param contactsDevice device in question
* @return date if existent as long, otherwise -1
*/
public abstract Date getDateOfLastReceivedMessage(OmemoManager omemoManager, OmemoDevice from);
public abstract Date getDateOfLastReceivedMessage(OmemoDevice userDevice, OmemoDevice contactsDevice);
/**
* Set the date in millis of the last time the signed preKey was renewed.
*
* @param omemoManager omemoManager of our device.
* @param userDevice our OmemoDevice.
* @param date date
*/
public abstract void setDateOfLastSignedPreKeyRenewal(OmemoManager omemoManager, Date date);
/**
* Store the date of the last preKey renewal in the omemoStore.
*
* @param omemoManager omemoManager of our device.
*/
public void setDateOfLastSignedPreKeyRenewal(OmemoManager omemoManager) {
setDateOfLastSignedPreKeyRenewal(omemoManager, new Date());
}
public abstract void setDateOfLastSignedPreKeyRenewal(OmemoDevice userDevice, Date date);
/**
* Get the date in millis of the last time the signed preKey was renewed.
*
* @param omemoManager omemoManager of our device.
* @param userDevice our OmemoDevice.
* @return date if existent, otherwise null
*/
public abstract Date getDateOfLastSignedPreKeyRenewal(OmemoManager omemoManager);
public abstract Date getDateOfLastSignedPreKeyRenewal(OmemoDevice userDevice);
/**
* Generate 'count' new PreKeys beginning with id 'startId'.
@ -531,37 +313,37 @@ public abstract class OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
* @param count how many keys do we want to generate
* @return Map of new preKeys
*/
public HashMap<Integer, T_PreKey> generateOmemoPreKeys(int startId, int count) {
public TreeMap<Integer, T_PreKey> generateOmemoPreKeys(int startId, int count) {
return keyUtil().generateOmemoPreKeys(startId, count);
}
/**
* Load the preKey with id 'preKeyId' from storage.
*
* @param omemoManager omemoManager of our device.
* @param userDevice our OmemoDevice.
* @param preKeyId id of the key to be loaded
* @return loaded preKey
*/
public abstract T_PreKey loadOmemoPreKey(OmemoManager omemoManager, int preKeyId);
public abstract T_PreKey loadOmemoPreKey(OmemoDevice userDevice, int preKeyId);
/**
* Store a PreKey in storage.
*
* @param omemoManager omemoManager of our device.
* @param userDevice our OmemoDevice.
* @param preKeyId id of the key
* @param preKey key
*/
public abstract void storeOmemoPreKey(OmemoManager omemoManager, int preKeyId, T_PreKey preKey);
public abstract void storeOmemoPreKey(OmemoDevice userDevice, int preKeyId, T_PreKey preKey);
/**
* Store a whole bunch of preKeys.
*
* @param omemoManager omemoManager of our device.
* @param userDevice our OmemoDevice.
* @param preKeyHashMap HashMap of preKeys
*/
public void storeOmemoPreKeys(OmemoManager omemoManager, HashMap<Integer, T_PreKey> preKeyHashMap) {
for (Map.Entry<Integer, T_PreKey> e : preKeyHashMap.entrySet()) {
storeOmemoPreKey(omemoManager, e.getKey(), e.getValue());
public void storeOmemoPreKeys(OmemoDevice userDevice, TreeMap<Integer, T_PreKey> preKeyHashMap) {
for (Map.Entry<Integer, T_PreKey> entry : preKeyHashMap.entrySet()) {
storeOmemoPreKey(userDevice, entry.getKey(), entry.getValue());
}
}
@ -569,52 +351,35 @@ public abstract class OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
* remove a preKey from storage. This is called, when a contact used one of our preKeys to establish a session
* with us.
*
* @param omemoManager omemoManager of our device.
* @param userDevice our OmemoDevice.
* @param preKeyId id of the used key that will be deleted
*/
public abstract void removeOmemoPreKey(OmemoManager omemoManager, int preKeyId);
/**
* Return the id of the currently used signed preKey.
* This is used to avoid collisions when generating a new signedPreKey.
*
* @param omemoManager omemoManager of our device.
* @return id
*/
public abstract int loadCurrentSignedPreKeyId(OmemoManager omemoManager);
/**
* Store the id of the currently used signedPreKey.
*
* @param omemoManager omemoManager of our device.
* @param currentSignedPreKeyId if of the signedPreKey that is currently in use
*/
public abstract void storeCurrentSignedPreKeyId(OmemoManager omemoManager, int currentSignedPreKeyId);
public abstract void removeOmemoPreKey(OmemoDevice userDevice, int preKeyId);
/**
* Return all our current OmemoPreKeys.
*
* @param omemoManager omemoManager of our device.
* @param userDevice our OmemoDevice.
* @return Map containing our preKeys
*/
public abstract HashMap<Integer, T_PreKey> loadOmemoPreKeys(OmemoManager omemoManager);
public abstract TreeMap<Integer, T_PreKey> loadOmemoPreKeys(OmemoDevice userDevice);
/**
* Return the signedPreKey with the id 'singedPreKeyId'.
*
* @param omemoManager omemoManager of our device.
* @param userDevice our OmemoDevice.
* @param signedPreKeyId id of the key
* @return key
*/
public abstract T_SigPreKey loadOmemoSignedPreKey(OmemoManager omemoManager, int signedPreKeyId);
public abstract T_SigPreKey loadOmemoSignedPreKey(OmemoDevice userDevice, int signedPreKeyId);
/**
* Load all our signed PreKeys.
*
* @param omemoManager omemoManager of our device.
* @param userDevice our OmemoDevice.
* @return HashMap of our singedPreKeys
*/
public abstract HashMap<Integer, T_SigPreKey> loadOmemoSignedPreKeys(OmemoManager omemoManager);
public abstract TreeMap<Integer, T_SigPreKey> loadOmemoSignedPreKeys(OmemoDevice userDevice);
/**
* Generate a new signed preKey.
@ -624,105 +389,109 @@ public abstract class OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
* @return signedPreKey
* @throws CorruptedOmemoKeyException when something goes wrong
*/
public T_SigPreKey generateOmemoSignedPreKey(T_IdKeyPair identityKeyPair, int signedPreKeyId) throws CorruptedOmemoKeyException {
public T_SigPreKey generateOmemoSignedPreKey(T_IdKeyPair identityKeyPair, int signedPreKeyId)
throws CorruptedOmemoKeyException
{
return keyUtil().generateOmemoSignedPreKey(identityKeyPair, signedPreKeyId);
}
/**
* Store a signedPreKey in storage.
*
* @param omemoManager omemoManager of our device.
* @param userDevice our OmemoDevice.
* @param signedPreKeyId id of the signedPreKey
* @param signedPreKey the key itself
*/
public abstract void storeOmemoSignedPreKey(OmemoManager omemoManager, int signedPreKeyId, T_SigPreKey signedPreKey);
public abstract void storeOmemoSignedPreKey(OmemoDevice userDevice, int signedPreKeyId, T_SigPreKey signedPreKey);
/**
* Remove a signedPreKey from storage.
*
* @param omemoManager omemoManager of our device.
* @param userDevice our OmemoDevice.
* @param signedPreKeyId id of the key that will be removed
*/
public abstract void removeOmemoSignedPreKey(OmemoManager omemoManager, int signedPreKeyId);
public abstract void removeOmemoSignedPreKey(OmemoDevice userDevice, int signedPreKeyId);
/**
* Load the crypto-lib specific session object of the device from storage.
*
* @param omemoManager omemoManager of our device.
* @param device device whose session we want to load
* @param userDevice our OmemoDevice.
* @param contactsDevice device whose session we want to load
* @return crypto related session
*/
public abstract T_Sess loadRawSession(OmemoManager omemoManager, OmemoDevice device);
public abstract T_Sess loadRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice);
/**
* Load all crypto-lib specific session objects of contact 'contact'.
*
* @param omemoManager omemoManager of our device.
* @param userDevice our OmemoDevice.
* @param contact BareJid of the contact we want to get all sessions from
* @return HashMap of deviceId and sessions of the contact
* @return TreeMap of deviceId and sessions of the contact
*/
public abstract HashMap<Integer, T_Sess> loadAllRawSessionsOf(OmemoManager omemoManager, BareJid contact);
public abstract HashMap<Integer, T_Sess> loadAllRawSessionsOf(OmemoDevice userDevice, BareJid contact);
/**
* Store a crypto-lib specific session to storage.
*
* @param omemoManager omemoManager of our device.
* @param device OmemoDevice whose session we want to store
* @param userDevice our OmemoDevice.
* @param contactsDevice OmemoDevice whose session we want to store
* @param session session
*/
public abstract void storeRawSession(OmemoManager omemoManager, OmemoDevice device, T_Sess session);
public abstract void storeRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice, T_Sess session);
/**
* Remove a crypto-lib specific session from storage.
*
* @param omemoManager omemoManager of our device.
* @param device device whose session we want to delete
* @param userDevice our OmemoDevice.
* @param contactsDevice device whose session we want to delete
*/
public abstract void removeRawSession(OmemoManager omemoManager, OmemoDevice device);
public abstract void removeRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice);
/**
* Remove all crypto-lib specific session of a contact.
*
* @param omemoManager omemoManager of our device.
* @param userDevice our OmemoDevice.
* @param contact BareJid of the contact
*/
public abstract void removeAllRawSessionsOf(OmemoManager omemoManager, BareJid contact);
public abstract void removeAllRawSessionsOf(OmemoDevice userDevice, BareJid contact);
/**
* Return true, if we have a session with the device, otherwise false.
* Hint for Signal: Do not try 'return getSession() != null' since this will create a new session.
*
* @param omemoManager omemoManager of our device.
* @param device device
* @param userDevice our OmemoDevice.
* @param contactsDevice device
* @return true if we have session, otherwise false
*/
public abstract boolean containsRawSession(OmemoManager omemoManager, OmemoDevice device);
public abstract boolean containsRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice);
/**
* Load a list of deviceIds from contact 'contact' from the local cache.
*
* @param omemoManager omemoManager of our device.
* @param userDevice our OmemoDevice.
* @param contact contact we want to get the deviceList of
* @return CachedDeviceList of the contact
*/
public abstract CachedDeviceList loadCachedDeviceList(OmemoManager omemoManager, BareJid contact);
public abstract CachedDeviceList loadCachedDeviceList(OmemoDevice userDevice, BareJid contact);
/**
* Store the DeviceList of the contact in local storage.
* See this as a cache.
*
* @param omemoManager omemoManager of our device.
* @param userDevice our OmemoDevice.
* @param contact Contact
* @param deviceList list of the contacts devices' ids.
* @param contactsDeviceList list of the contacts devices' ids.
*/
public abstract void storeCachedDeviceList(OmemoManager omemoManager, BareJid contact, CachedDeviceList deviceList);
public abstract void storeCachedDeviceList(OmemoDevice userDevice,
BareJid contact,
CachedDeviceList contactsDeviceList);
/**
* Delete this device's IdentityKey, PreKeys, SignedPreKeys and Sessions.
*
* @param omemoManager omemoManager of our device.
* @param userDevice our OmemoDevice.
*/
public abstract void purgeOwnDeviceKeys(OmemoManager omemoManager);
public abstract void purgeOwnDeviceKeys(OmemoDevice userDevice);
/**
* Return a concrete KeyUtil object that we can use as a utility to create keys etc.
@ -734,58 +503,43 @@ public abstract class OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
/**
* Return our identityKeys fingerprint.
*
* @param omemoManager omemoManager of our device.
* @param userDevice our OmemoDevice.
* @return fingerprint of our identityKeyPair
*/
public OmemoFingerprint getFingerprint(OmemoManager omemoManager) {
try {
return keyUtil().getFingerprint(keyUtil().identityKeyFromPair(loadOmemoIdentityKeyPair(omemoManager)));
} catch (CorruptedOmemoKeyException e) {
LOGGER.log(Level.WARNING, "getFingerprint failed due to corrupted identityKeyPair: " + e.getMessage());
public OmemoFingerprint getFingerprint(OmemoDevice userDevice)
throws CorruptedOmemoKeyException
{
T_IdKeyPair keyPair = loadOmemoIdentityKeyPair(userDevice);
if (keyPair == null) {
return null;
}
return keyUtil().getFingerprintOfIdentityKey(keyUtil().identityKeyFromPair(keyPair));
}
/**
* Return the default deviceId for a user.
* The defaultDeviceId will be used when the OmemoManager gets instantiated without passing a specific deviceId.
* If no default id is set, return -1;
*
* @param user user
* @return defaultDeviceId or -1
*/
public abstract int getDefaultDeviceId(BareJid user);
/**
* Set the default deviceId of a user.
*
* @param user user
* @param defaultDeviceId defaultDeviceId
*/
public abstract void setDefaultDeviceId(BareJid user, int defaultDeviceId);
/**
* Return the fingerprint of the given devices announced identityKey.
*
* @param omemoManager omemoManager of our device.
* @param device device
* @param managerGuard omemoManager of our device.
* @param contactsDevice device
* @throws CannotEstablishOmemoSessionException if we cannot establish a session
* @return fingerprint of the identityKey
*/
public OmemoFingerprint getFingerprint(OmemoManager omemoManager, OmemoDevice device) throws CannotEstablishOmemoSessionException {
T_IdKey idKey;
try {
idKey = loadOmemoIdentityKey(omemoManager, device);
if (idKey == null) {
OmemoService.getInstance().buildSessionFromOmemoBundle(omemoManager, device, true);
public OmemoFingerprint getFingerprint(OmemoManager.KnownBareJidGuard managerGuard, OmemoDevice contactsDevice)
throws CannotEstablishOmemoSessionException, CorruptedOmemoKeyException
{
OmemoManager omemoManager = managerGuard.get();
T_IdKey identityKey = loadOmemoIdentityKey(omemoManager.getOwnDevice(), contactsDevice);
if (identityKey == null) {
// Key cannot be loaded. Maybe it doesn't exist. Fetch a bundle to get it...
OmemoService.getInstance().buildSessionWithDevice(managerGuard, contactsDevice, true);
}
idKey = loadOmemoIdentityKey(omemoManager, device);
} catch (CorruptedOmemoKeyException e) {
LOGGER.log(Level.WARNING, "getFingerprint failed due to corrupted identityKey: " + e.getMessage());
identityKey = loadOmemoIdentityKey(omemoManager.getOwnDevice(), contactsDevice);
if (identityKey == null) {
return null;
}
return keyUtil().getFingerprint(idKey);
return keyUtil().getFingerprintOfIdentityKey(identityKey);
}
}

View file

@ -0,0 +1,40 @@
/**
*
* 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.exceptions;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
public class MultipleIOException extends IOException {
private static final long serialVersionUID = 1L;
private final ArrayList<IOException> exceptions = new ArrayList<>();
public MultipleIOException(IOException... exceptions) {
this.exceptions.addAll(Arrays.asList(exceptions));
}
public void addException(IOException e) {
exceptions.add(e);
}
public ArrayList<IOException> getExceptions() {
return exceptions;
}
}

View file

@ -0,0 +1,68 @@
/**
*
* 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.exceptions;
import org.jivesoftware.smackx.omemo.OmemoFingerprint;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
/**
* Exception that gets thrown when we try to decrypt a message which contains an identityKey that differs from the one
* we previously trusted.
*/
public class UntrustedOmemoIdentityException extends Exception {
private static final long serialVersionUID = 1L;
private final OmemoDevice device;
private final OmemoFingerprint trustedKey, untrustedKey;
/**
* Constructor.
* @param device device which sent the message.
* @param fpTrusted fingerprint of the identityKey we previously had and trusted.
* @param fpUntrusted fingerprint of the new key which is untrusted.
*/
public UntrustedOmemoIdentityException(OmemoDevice device, OmemoFingerprint fpTrusted, OmemoFingerprint fpUntrusted) {
super();
this.device = device;
this.trustedKey = fpTrusted;
this.untrustedKey = fpUntrusted;
}
/**
* Return the device which sent the message.
* @return omemoDevice.
*/
public OmemoDevice getDevice() {
return device;
}
/**
* Return the fingerprint of the key we expected.
* @return
*/
public OmemoFingerprint getTrustedFingerprint() {
return trustedKey;
}
/**
* Return the fingerprint of the unexpected untrusted key.
* @return
*/
public OmemoFingerprint getUntrustedFingerprint() {
return untrustedKey;
}
}

View file

@ -43,6 +43,16 @@ public class CachedDeviceList implements Serializable {
this.inactiveDevices = new HashSet<>();
}
public CachedDeviceList(Set<Integer> activeDevices, Set<Integer> inactiveDevices) {
this();
this.activeDevices.addAll(activeDevices);
this.inactiveDevices.addAll(inactiveDevices);
}
public CachedDeviceList(CachedDeviceList original) {
this(original.getActiveDevices(), original.getInactiveDevices());
}
/**
* Returns all active devices.
* Active devices are all devices that were in the latest DeviceList update.

View file

@ -16,6 +16,8 @@
*/
package org.jivesoftware.smackx.omemo.internal;
import org.jivesoftware.smackx.omemo.OmemoFingerprint;
/**
* Class that contains information about a decrypted message (eg. which key was used, if it was a carbon...).
*
@ -23,7 +25,7 @@ package org.jivesoftware.smackx.omemo.internal;
*/
public class OmemoMessageInformation {
private boolean isOmemoMessage;
private IdentityKeyWrapper senderIdentityKey;
private OmemoFingerprint senderFingerprint;
private OmemoDevice senderDevice;
private CARBON carbon = CARBON.NONE;
@ -37,12 +39,12 @@ public class OmemoMessageInformation {
/**
* Creates a new OmemoMessageInformation object. Its assumed, that this is about an OMEMO message.
*
* @param senderIdentityKey identityKey of the sender device
* @param senderFingerprint fingerprint of the identityKey of the sender device
* @param senderDevice device that sent the message
* @param carbon Carbon type
*/
public OmemoMessageInformation(IdentityKeyWrapper senderIdentityKey, OmemoDevice senderDevice, CARBON carbon) {
this.senderIdentityKey = senderIdentityKey;
public OmemoMessageInformation(OmemoFingerprint senderFingerprint, OmemoDevice senderDevice, CARBON carbon) {
this.senderFingerprint = senderFingerprint;
this.senderDevice = senderDevice;
this.carbon = carbon;
this.isOmemoMessage = true;
@ -51,13 +53,13 @@ public class OmemoMessageInformation {
/**
* Create a new OmemoMessageInformation.
*
* @param senderIdentityKey identityKey of the sender device
* @param senderFingerprint fingerprint of the identityKey of the sender device
* @param senderDevice device that sent the message
* @param carbon Carbon type
* @param omemo is this an omemo message?
*/
public OmemoMessageInformation(IdentityKeyWrapper senderIdentityKey, OmemoDevice senderDevice, CARBON carbon, boolean omemo) {
this(senderIdentityKey, senderDevice, carbon);
public OmemoMessageInformation(OmemoFingerprint senderFingerprint, OmemoDevice senderDevice, CARBON carbon, boolean omemo) {
this(senderFingerprint, senderDevice, carbon);
this.isOmemoMessage = omemo;
}
@ -66,17 +68,17 @@ public class OmemoMessageInformation {
*
* @return identityKey
*/
public IdentityKeyWrapper getSenderIdentityKey() {
return senderIdentityKey;
public OmemoFingerprint getSenderFingerprint() {
return senderFingerprint;
}
/**
* Set the sender devices identityKey.
*
* @param senderIdentityKey identityKey
* @param senderFingerprint fingerprint of the senders identityKey.
*/
public void setSenderIdentityKey(IdentityKeyWrapper senderIdentityKey) {
this.senderIdentityKey = senderIdentityKey;
public void setSenderFingerprint(OmemoFingerprint senderFingerprint) {
this.senderFingerprint = senderFingerprint;
}
/**

View file

@ -1,266 +0,0 @@
/**
*
* 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.internal;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.omemo.OmemoFingerprint;
import org.jivesoftware.smackx.omemo.OmemoManager;
import org.jivesoftware.smackx.omemo.OmemoStore;
import org.jivesoftware.smackx.omemo.element.OmemoElement;
import org.jivesoftware.smackx.omemo.element.OmemoElement.OmemoHeader.Key;
import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
import org.jivesoftware.smackx.omemo.exceptions.MultipleCryptoFailedException;
import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException;
/**
* This class represents a OMEMO session between us and another device.
*
* @param <T_IdKeyPair> IdentityKeyPair class
* @param <T_IdKey> IdentityKey class
* @param <T_PreKey> PreKey class
* @param <T_SigPreKey> SignedPreKey class
* @param <T_Sess> Session class
* @param <T_Addr> Address class
* @param <T_ECPub> Elliptic Curve PublicKey class
* @param <T_Bundle> Bundle class
* @param <T_Ciph> Cipher class
* @author Paul Schaub
*/
public abstract class OmemoSession<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> {
protected final T_Ciph cipher;
protected final OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore;
protected final OmemoDevice remoteDevice;
protected final OmemoManager omemoManager;
protected T_IdKey identityKey;
protected int preKeyId = -1;
/**
* Constructor used when we establish the session.
*
* @param omemoManager OmemoManager of our device
* @param omemoStore OmemoStore where we want to store the session and get key information from
* @param remoteDevice the OmemoDevice we want to establish the session with
* @param identityKey identityKey of the recipient
*/
public OmemoSession(OmemoManager omemoManager,
OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore,
OmemoDevice remoteDevice, T_IdKey identityKey) {
this(omemoManager, omemoStore, remoteDevice);
this.identityKey = identityKey;
}
/**
* Another constructor used when they establish the session with us.
*
* @param omemoManager OmemoManager of our device
* @param omemoStore OmemoStore we want to store the session and their key in
* @param remoteDevice identityKey of the partner
*/
public OmemoSession(OmemoManager omemoManager, OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore,
OmemoDevice remoteDevice) {
this.omemoManager = omemoManager;
this.omemoStore = omemoStore;
this.remoteDevice = remoteDevice;
this.cipher = createCipher(remoteDevice);
}
/**
* Try to decrypt the transported message key using the double ratchet session.
*
* @param element omemoElement
* @param keyId our keyId
* @return tuple of cipher generated from the unpacked message key and the authtag
* @throws CryptoFailedException if decryption using the double ratchet fails
* @throws NoRawSessionException if we have no session, but the element was NOT a PreKeyMessage
*/
public CipherAndAuthTag decryptTransportedKey(OmemoElement element, int keyId) throws CryptoFailedException,
NoRawSessionException {
byte[] unpackedKey = null;
List<CryptoFailedException> decryptExceptions = new ArrayList<>();
List<Key> keys = element.getHeader().getKeys();
// Find key with our ID.
for (OmemoElement.OmemoHeader.Key k : keys) {
if (k.getId() == keyId) {
try {
unpackedKey = decryptMessageKey(k.getData());
break;
} catch (CryptoFailedException e) {
// There might be multiple keys with our id, but we can only decrypt one.
// So we can't throw the exception, when decrypting the first duplicate which is not for us.
decryptExceptions.add(e);
}
}
}
if (unpackedKey == null) {
if (!decryptExceptions.isEmpty()) {
throw MultipleCryptoFailedException.from(decryptExceptions);
}
throw new CryptoFailedException("Transported key could not be decrypted, since no provided message key. Provides keys: " + keys);
}
byte[] messageKey = new byte[16];
byte[] authTag = null;
if (unpackedKey.length == 32) {
authTag = new byte[16];
// copy key part into messageKey
System.arraycopy(unpackedKey, 0, messageKey, 0, 16);
// copy tag part into authTag
System.arraycopy(unpackedKey, 16, authTag, 0,16);
} else if (element.isKeyTransportElement() && unpackedKey.length == 16) {
messageKey = unpackedKey;
} else {
throw new CryptoFailedException("MessageKey has wrong length: "
+ unpackedKey.length + ". Probably legacy auth tag format.");
}
return new CipherAndAuthTag(messageKey, element.getHeader().getIv(), authTag);
}
/**
* Use the symmetric key in cipherAndAuthTag to decrypt the payload of the omemoMessage.
* The decrypted payload will be the body of the returned Message.
*
* @param element omemoElement containing a payload.
* @param cipherAndAuthTag cipher and authentication tag.
* @return Message containing the decrypted payload in its body.
* @throws CryptoFailedException
*/
public static Message decryptMessageElement(OmemoElement element, CipherAndAuthTag cipherAndAuthTag) throws CryptoFailedException {
if (!element.isMessageElement()) {
throw new IllegalArgumentException("decryptMessageElement cannot decrypt OmemoElement which is no MessageElement!");
}
if (cipherAndAuthTag.getAuthTag() == null || cipherAndAuthTag.getAuthTag().length != 16) {
throw new CryptoFailedException("AuthenticationTag is null or has wrong length: "
+ (cipherAndAuthTag.getAuthTag() == null ? "null" : cipherAndAuthTag.getAuthTag().length));
}
byte[] encryptedBody = new byte[element.getPayload().length + 16];
byte[] payload = element.getPayload();
System.arraycopy(payload, 0, encryptedBody, 0, payload.length);
System.arraycopy(cipherAndAuthTag.getAuthTag(), 0, encryptedBody, payload.length, 16);
try {
String plaintext = new String(cipherAndAuthTag.getCipher().doFinal(encryptedBody), StringUtils.UTF8);
Message decrypted = new Message();
decrypted.setBody(plaintext);
return decrypted;
} catch (UnsupportedEncodingException | IllegalBlockSizeException | BadPaddingException e) {
throw new CryptoFailedException("decryptMessageElement could not decipher message body: "
+ e.getMessage());
}
}
/**
* Try to decrypt the message.
* First decrypt the message key using our session with the sender.
* Second use the decrypted key to decrypt the message.
* The decrypted content of the 'encrypted'-element becomes the body of the clear text message.
*
* @param element OmemoElement
* @param keyId the key we want to decrypt (usually our own device id)
* @return message as plaintext
* @throws CryptoFailedException
* @throws NoRawSessionException
*/
// TODO find solution for what we actually want to decrypt (String, Message, List<ExtensionElements>...)
public Message decryptMessageElement(OmemoElement element, int keyId) throws CryptoFailedException, NoRawSessionException {
if (!element.isMessageElement()) {
throw new IllegalArgumentException("OmemoElement is not a messageElement!");
}
CipherAndAuthTag cipherAndAuthTag = decryptTransportedKey(element, keyId);
return decryptMessageElement(element, cipherAndAuthTag);
}
/**
* Create a new SessionCipher used to encrypt/decrypt keys. The cipher typically implements the ratchet and KDF-chains.
*
* @param contact OmemoDevice
* @return SessionCipher
*/
public abstract T_Ciph createCipher(OmemoDevice contact);
/**
* Get the id of the preKey used to establish the session.
*
* @return id
*/
public int getPreKeyId() {
return this.preKeyId;
}
/**
* Encrypt a message key for the recipient. This key can be deciphered by the recipient with its corresponding
* session cipher. The key is then used to decipher the message.
*
* @param messageKey serialized key to encrypt
* @return A CiphertextTuple containing the ciphertext and the messageType
* @throws CryptoFailedException
*/
public abstract CiphertextTuple encryptMessageKey(byte[] messageKey) throws CryptoFailedException;
/**
* Decrypt a messageKey using our sessionCipher. We can use that key to decipher the actual message.
* Same as encryptMessageKey, just the other way round.
*
* @param encryptedKey encrypted key
* @return serialized decrypted key or null
* @throws CryptoFailedException when decryption fails.
* @throws NoRawSessionException when no session was found in the double ratchet library
*/
public abstract byte[] decryptMessageKey(byte[] encryptedKey) throws CryptoFailedException, NoRawSessionException;
/**
* Return the identityKey of the session.
*
* @return identityKey
*/
public T_IdKey getIdentityKey() {
return identityKey;
}
/**
* Set the identityKey of the remote device.
* @param identityKey identityKey
*/
public void setIdentityKey(T_IdKey identityKey) {
this.identityKey = identityKey;
}
/**
* Return the fingerprint of the contacts identityKey.
*
* @return fingerprint or null
*/
public OmemoFingerprint getFingerprint() {
return (this.identityKey != null ? omemoStore.keyUtil().getFingerprint(this.identityKey) : null);
}
}

View file

@ -0,0 +1,29 @@
/**
*
* 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.internal.listener;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smackx.carbons.packet.CarbonExtension;
import org.jivesoftware.smackx.omemo.OmemoManager;
/**
* Internal listener for OMEMO encrypted carbon copies.
*/
public interface OmemoCarbonCopyStanzaReceivedListener {
void onOmemoCarbonCopyReceived(CarbonExtension.Direction direction, Message carbonCopy, Message wrappingMessage, OmemoManager.KnownBareJidGuard omemoManager);
}

View file

@ -0,0 +1,25 @@
/**
*
* 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.internal.listener;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smackx.omemo.OmemoManager;
public interface OmemoMessageStanzaReceivedListener {
void onOmemoMessageStanzaReceived(Stanza stanza, OmemoManager.KnownBareJidGuard omemoManager);
}

View file

@ -14,23 +14,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.omemo.internal;
/**
* Wrapper for IdentityKey objects.
* StanzaListeners used for internal purposes.
*
* @author Paul Schaub
* @see <a href="https://conversations.im/xeps/multi-end.html">XEP-0384: OMEMO</a>
*/
public class IdentityKeyWrapper {
private final Object identityKey;
public IdentityKeyWrapper(Object wrapped) {
identityKey = wrapped;
}
public Object getIdentityKey() {
return identityKey;
}
}
package org.jivesoftware.smackx.omemo.internal.listener;

View file

@ -0,0 +1,28 @@
/**
*
* 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.trust;
import org.jivesoftware.smackx.omemo.OmemoFingerprint;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
public interface TrustCallback {
TrustState getTrust(OmemoDevice device, OmemoFingerprint fingerprint);
void setTrust(OmemoDevice device, OmemoFingerprint fingerprint, TrustState state);
}

View file

@ -0,0 +1,21 @@
/**
*
* 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.trust;
public enum TrustState {
undecided, untrusted, trusted
}

View file

@ -0,0 +1,23 @@
/**
*
* 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.
*/
/**
* Callbacks used to pass trust decisions up to the client.
*
* @author Paul Schaub
* @see <a href="https://conversations.im/xeps/multi-end.html">XEP-0384: OMEMO</a>
*/
package org.jivesoftware.smackx.omemo.trust;

View file

@ -19,16 +19,14 @@ package org.jivesoftware.smackx.omemo.util;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smackx.omemo.OmemoFingerprint;
import org.jivesoftware.smackx.omemo.OmemoManager;
import org.jivesoftware.smackx.omemo.OmemoStore;
import org.jivesoftware.smackx.omemo.element.OmemoBundleVAxolotlElement;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.internal.OmemoSession;
import org.jxmpp.stringprep.XmppStringprepException;
@ -207,7 +205,7 @@ public abstract class OmemoKeyUtil<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
* @param count how many keys do we want to generate
* @return Map of new preKeys
*/
public abstract HashMap<Integer, T_PreKey> generateOmemoPreKeys(int startId, int count);
public abstract TreeMap<Integer, T_PreKey> generateOmemoPreKeys(int startId, int count);
/**
* Generate a new signed preKey.
@ -330,7 +328,7 @@ public abstract class OmemoKeyUtil<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
* @param preKeyHashMap HashMap of preKeys
* @return HashMap of byte arrays but with the same keyIds as key
*/
public HashMap<Integer, byte[]> preKeyPublisKeysForBundle(HashMap<Integer, T_PreKey> preKeyHashMap) {
public HashMap<Integer, byte[]> preKeyPublisKeysForBundle(TreeMap<Integer, T_PreKey> preKeyHashMap) {
HashMap<Integer, byte[]> out = new HashMap<>();
for (Map.Entry<Integer, T_PreKey> e : preKeyHashMap.entrySet()) {
out.put(e.getKey(), preKeyForBundle(e.getValue()));
@ -352,32 +350,14 @@ public abstract class OmemoKeyUtil<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
* @param identityKey identityKey
* @return fingerprint of the key
*/
public abstract OmemoFingerprint getFingerprint(T_IdKey identityKey);
public abstract OmemoFingerprint getFingerprintOfIdentityKey(T_IdKey identityKey);
/**
* Create a new crypto-specific Session object.
*
* @param omemoManager omemoManager of our device.
* @param omemoStore omemoStore where we can save the session, get keys from etc.
* @param from the device we want to create the session with.
* @return a new session
* Returns the fingerprint of the public key of an identityKeyPair.
* @param identityKeyPair IdentityKeyPair.
* @return fingerprint of the public key.
*/
public abstract OmemoSession<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
createOmemoSession(OmemoManager omemoManager, OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore,
OmemoDevice from);
/**
* Create a new concrete OmemoSession with a contact.
*
* @param omemoManager omemoManager of our device.
* @param omemoStore omemoStore
* @param device device to establish the session with
* @param identityKey identityKey of the device
* @return concrete OmemoSession
*/
public abstract OmemoSession<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
createOmemoSession(OmemoManager omemoManager, OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore,
OmemoDevice device, T_IdKey identityKey);
public abstract OmemoFingerprint getFingerprintOfIdentityKeyPair(T_IdKeyPair identityKeyPair);
/**
* Deserialize a raw OMEMO Session from bytes.

View file

@ -28,7 +28,6 @@ import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.util.ArrayList;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
@ -38,17 +37,18 @@ import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.omemo.OmemoFingerprint;
import org.jivesoftware.smackx.omemo.OmemoManager;
import org.jivesoftware.smackx.omemo.OmemoStore;
import org.jivesoftware.smackx.omemo.OmemoSessionManager;
import org.jivesoftware.smackx.omemo.element.OmemoVAxolotlElement;
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.CiphertextTuple;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.internal.OmemoSession;
/**
* Class used to build OMEMO messages.
@ -65,8 +65,8 @@ import org.jivesoftware.smackx.omemo.internal.OmemoSession;
* @author Paul Schaub
*/
public class OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> {
private final OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore;
private final OmemoManager omemoManager;
private final OmemoSessionManager<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> sessionManager;
private final OmemoManager.KnownBareJidGuard managerGuard;
private byte[] messageKey = generateKey();
private byte[] initializationVector = generateIv();
@ -77,8 +77,8 @@ public class OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
/**
* Create a OmemoMessageBuilder.
*
* @param omemoManager OmemoManager of our device.
* @param omemoStore OmemoStore.
* @param managerGuard OmemoManager of our device.
* @param sessionManager OmemoSessionManager.
* @param aesKey AES key that will be transported to the recipient. This is used eg. to encrypt the body.
* @param iv IV
* @throws NoSuchPaddingException
@ -90,13 +90,13 @@ public class OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
* @throws NoSuchProviderException
* @throws InvalidAlgorithmParameterException
*/
public OmemoMessageBuilder(OmemoManager omemoManager,
OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore,
public OmemoMessageBuilder(OmemoManager.KnownBareJidGuard managerGuard,
OmemoSessionManager<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> sessionManager,
byte[] aesKey, byte[] iv)
throws NoSuchPaddingException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException,
UnsupportedEncodingException, NoSuchProviderException, InvalidAlgorithmParameterException {
this.omemoStore = omemoStore;
this.omemoManager = omemoManager;
this.managerGuard = managerGuard;
this.sessionManager = sessionManager;
this.messageKey = aesKey;
this.initializationVector = iv;
}
@ -104,8 +104,8 @@ public class OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
/**
* Create a new OmemoMessageBuilder with random IV and AES key.
*
* @param omemoManager omemoManager of our device.
* @param omemoStore omemoStore.
* @param managerGuard omemoManager of our device.
* @param sessionManager omemoSessionManager.
* @param message Messages body.
* @throws NoSuchPaddingException
* @throws BadPaddingException
@ -116,12 +116,13 @@ public class OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
* @throws NoSuchProviderException
* @throws InvalidAlgorithmParameterException
*/
public OmemoMessageBuilder(OmemoManager omemoManager,
OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore, String message)
public OmemoMessageBuilder(OmemoManager.KnownBareJidGuard managerGuard,
OmemoSessionManager<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> sessionManager,
String message)
throws NoSuchPaddingException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException,
UnsupportedEncodingException, NoSuchProviderException, InvalidAlgorithmParameterException {
this.omemoManager = omemoManager;
this.omemoStore = omemoStore;
this.managerGuard = managerGuard;
this.sessionManager = sessionManager;
this.setMessage(message);
}
@ -175,34 +176,40 @@ public class OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
* @throws UndecidedOmemoIdentityException
* @throws CorruptedOmemoKeyException
*/
public void addRecipient(OmemoDevice device) throws CryptoFailedException, UndecidedOmemoIdentityException, CorruptedOmemoKeyException {
public void addRecipient(OmemoDevice device)
throws CryptoFailedException, UndecidedOmemoIdentityException, CorruptedOmemoKeyException,
CannotEstablishOmemoSessionException {
addRecipient(device, false);
}
/**
* Add a new recipient device to the message.
* @param device recipient device
* @param contactsDevice recipient device
* @param ignoreTrust ignore current trust state? Useful for keyTransportMessages that are sent to repair a session
* @throws CryptoFailedException
* @throws UndecidedOmemoIdentityException
* @throws CorruptedOmemoKeyException
*/
public void addRecipient(OmemoDevice device, boolean ignoreTrust) throws
CryptoFailedException, UndecidedOmemoIdentityException, CorruptedOmemoKeyException {
OmemoSession<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> session =
omemoStore.getOmemoSessionOf(omemoManager, device);
public void addRecipient(OmemoDevice contactsDevice, boolean ignoreTrust) throws
CryptoFailedException, UndecidedOmemoIdentityException, CorruptedOmemoKeyException,
CannotEstablishOmemoSessionException {
if (session != null) {
if (!ignoreTrust && !omemoStore.isDecidedOmemoIdentity(omemoManager, device, session.getIdentityKey())) {
OmemoFingerprint fingerprint;
try {
fingerprint = managerGuard.get().getFingerprint(contactsDevice);
} catch (SmackException.NotLoggedInException e) {
throw new AssertionError("This should never happen.");
}
if (!ignoreTrust && !managerGuard.get().isDecidedOmemoIdentity(contactsDevice, fingerprint)) {
// Warn user of undecided device
throw new UndecidedOmemoIdentityException(device);
throw new UndecidedOmemoIdentityException(contactsDevice);
}
if (!ignoreTrust && omemoStore.isTrustedOmemoIdentity(omemoManager, device, session.getIdentityKey())) {
if (ignoreTrust || managerGuard.get().isTrustedOmemoIdentity(contactsDevice, fingerprint)) {
// Encrypt key and save to header
CiphertextTuple encryptedKey = session.encryptMessageKey(messageKey);
keys.add(new OmemoVAxolotlElement.OmemoHeader.Key(encryptedKey.getCiphertext(), device.getDeviceId(), encryptedKey.isPreKeyMessage()));
}
CiphertextTuple encryptedKey = sessionManager.doubleRatchetEncrypt(contactsDevice, messageKey);
keys.add(new OmemoVAxolotlElement.OmemoHeader.Key(encryptedKey.getCiphertext(), contactsDevice.getDeviceId(), encryptedKey.isPreKeyMessage()));
}
}
@ -213,7 +220,7 @@ public class OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
*/
public OmemoVAxolotlElement finish() {
OmemoVAxolotlElement.OmemoHeader header = new OmemoVAxolotlElement.OmemoHeader(
omemoManager.getDeviceId(),
managerGuard.get().getDeviceId(),
keys,
initializationVector
);

View file

@ -16,6 +16,7 @@
*/
package org.jivesoftware.smack.omemo;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@ -67,5 +68,11 @@ public class DeviceListTest {
!cached.getInactiveDevices().contains(4));
assertTrue(cached.getAllDevices().size() == 4);
assertFalse(cached.contains(17));
cached.addDevice(17);
assertTrue(cached.getActiveDevices().contains(17));
assertNotNull(cached.toString());
}
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,43 @@
/**
*
* 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.smack.omemo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation;
import org.junit.Test;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
public class OmemoMessageInformationTest {
@Test
public void setterGetterTest() throws XmppStringprepException {
OmemoMessageInformation information = new OmemoMessageInformation();
assertEquals(information.getCarbon(), OmemoMessageInformation.CARBON.NONE);
information.setCarbon(OmemoMessageInformation.CARBON.RECV);
assertEquals(OmemoMessageInformation.CARBON.RECV, information.getCarbon());
assertNull(information.getSenderDevice());
OmemoDevice device = new OmemoDevice(JidCreate.bareFrom("test@a.bc"), 14);
information.setSenderDevice(device);
assertEquals(device, information.getSenderDevice());
}
}

View file

@ -0,0 +1,346 @@
/**
*
* Copyright 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.smack.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.assertSame;
import static junit.framework.TestCase.assertTrue;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.TreeMap;
import org.jivesoftware.smackx.omemo.FileBasedOmemoStore;
import org.jivesoftware.smackx.omemo.OmemoFingerprint;
import org.jivesoftware.smackx.omemo.OmemoStore;
import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
import org.jivesoftware.smackx.omemo.internal.CachedDeviceList;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.junit.AfterClass;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
public abstract class OmemoStoreTest<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> {
protected final OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> store;
private final OmemoDevice alice, bob;
private static final TemporaryFolder tmp = initStaticTemp();
OmemoStoreTest(OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> store)
throws XmppStringprepException {
this.store = store;
alice = new OmemoDevice(JidCreate.bareFrom("alice@wonderland.lit"), 123);
bob = new OmemoDevice(JidCreate.bareFrom("bob@builder.tv"), 987);
}
// Tests
@Test
public void keyUtilNotNull() {
assertNotNull(store.keyUtil());
}
@Test
public void generateOmemoIdentityKeyPairDoesNotReturnNull() {
assertNotNull(store.generateOmemoIdentityKeyPair());
}
@Test
public void identityKeyFromIdentityKeyPairIsNotNull() {
T_IdKeyPair pair = store.generateOmemoIdentityKeyPair();
assertNotNull(store.keyUtil().identityKeyFromPair(pair));
}
@Test
public void storeLoadRemoveOmemoIdentityKeyPair()
throws IOException, CorruptedOmemoKeyException
{
T_IdKeyPair before = store.generateOmemoIdentityKeyPair();
assertNull(store.loadOmemoIdentityKeyPair(alice));
store.storeOmemoIdentityKeyPair(alice, before);
T_IdKeyPair after = store.loadOmemoIdentityKeyPair(alice);
assertNotNull(after);
// Fingerprints equal
assertEquals(store.keyUtil().getFingerprintOfIdentityKeyPair(before),
store.keyUtil().getFingerprintOfIdentityKeyPair(after));
// Byte-representation equals
assertTrue(Arrays.equals(
store.keyUtil().identityKeyPairToBytes(before),
store.keyUtil().identityKeyPairToBytes(after)));
// Non-existing keypair
assertNull("Must return null for non-existing key pairs.", store.loadOmemoIdentityKeyPair(bob));
// Deleting works
store.removeOmemoIdentityKeyPair(alice);
assertNull(store.loadOmemoIdentityKeyPair(alice));
}
@Test
public void storeLoadRemoveOmemoIdentityKey()
throws IOException, CorruptedOmemoKeyException
{
// Create IdentityKeys and get bytes
T_IdKey keyA1 = store.keyUtil().identityKeyFromPair(store.generateOmemoIdentityKeyPair());
T_IdKey keyB1 = store.keyUtil().identityKeyFromPair(store.generateOmemoIdentityKeyPair());
byte[] bytesA1 = store.keyUtil().identityKeyToBytes(keyA1);
byte[] bytesB = store.keyUtil().identityKeyToBytes(keyB1);
// Not null and not of length 0
assertNotNull("Serialized identityKey cannot be null.", bytesA1);
assertNotNull("Serialized identityKey cannot be null.", bytesB);
assertNotSame("Serialized identityKey must be of length > 0.", 0, bytesA1.length);
assertNotSame("Serialized identityKey must be of length > 0.", 0, bytesB.length);
// Keys do not equal
assertFalse("Generated IdentityKeys must not be equal (ULTRA unlikely).",
Arrays.equals(bytesA1, bytesB));
// Loading must return null before and not null after saving
assertNull("Must return null, the store could not have this key by now.",
store.loadOmemoIdentityKey(alice, bob));
store.storeOmemoIdentityKey(alice, bob, keyA1);
T_IdKey keyA2 = store.loadOmemoIdentityKey(alice, bob);
assertNotNull(keyA2);
// Loaded key must equal stored one
byte[] bytesA2 = store.keyUtil().identityKeyToBytes(keyA2);
assertTrue("Serialized loaded key must equal serialized stored one.",
Arrays.equals(bytesA1, bytesA2));
// Non-existing keys must return null
assertNull("Non-existing keys must be returned as null.", store.loadOmemoIdentityKey(bob, alice));
// Key must vanish when deleted.
store.removeOmemoIdentityKey(alice, bob);
assertNull(store.loadOmemoIdentityKey(alice, bob));
}
@Test
public void generateOmemoPreKeys() {
TreeMap<Integer, T_PreKey> keys = store.generateOmemoPreKeys(31, 49);
assertNotNull("Generated data structure must not be null.", keys);
byte[] lastKey = null;
for (int i = 31; i <= 79; i++) {
assertEquals("Key ids must be ascending order, starting at 31.", Integer.valueOf(i), keys.firstKey());
assertNotNull("Every id must match to a key.", keys.get(keys.firstKey()));
byte[] bytes = store.keyUtil().preKeyToBytes(keys.get(keys.firstKey()));
assertNotNull("Serialized preKey must not be null.", bytes);
assertNotSame("Serialized preKey must not be of length 0.", 0, bytes.length);
if (lastKey != null) {
assertFalse("PreKeys MUST NOT be equal.", Arrays.equals(lastKey, bytes));
}
lastKey = bytes;
keys.remove(keys.firstKey());
}
assertEquals("After deleting 49 keys, there must be no keys left.", 0, keys.size());
}
@Test
public void storeLoadRemoveOmemoPreKeys()
throws IOException, InterruptedException
{
TreeMap<Integer, T_PreKey> before = store.generateOmemoPreKeys(1, 10);
assertEquals("The store must have no prekeys before this test.", 0, store.loadOmemoPreKeys(alice).size());
store.storeOmemoPreKeys(alice, before);
TreeMap<Integer, T_PreKey> after = store.loadOmemoPreKeys(alice);
assertNotNull("Loaded preKeys must not be null.", after);
assertEquals("Loaded preKey count must equal stored count.", before.size(), after.size());
// Non-existing key must be returned as null
assertNull("Non-existing preKey must be returned as null.", store.loadOmemoPreKey(alice, 10000));
int last = after.size();
for (int i = 1; i <= last; i++) {
T_PreKey bKey = before.get(i);
T_PreKey aKey = after.get(i);
assertTrue("Loaded keys must equal stored ones.", Arrays.equals(
store.keyUtil().preKeyToBytes(bKey),
store.keyUtil().preKeyToBytes(aKey)));
T_PreKey rKey = store.loadOmemoPreKey(alice, i);
assertNotNull("Randomly accessed preKeys must not be null.", rKey);
assertTrue("Randomly accessed preKeys must equal the stored ones.", Arrays.equals(
store.keyUtil().preKeyToBytes(aKey),
store.keyUtil().preKeyToBytes(rKey)));
store.removeOmemoPreKey(alice, i);
assertNull("PreKey must be null after deletion.", store.loadOmemoPreKey(alice, i));
}
TreeMap<Integer, T_PreKey> postDeletion = store.loadOmemoPreKeys(alice);
assertSame("PreKey count must equal 0 after deletion of all keys.", 0, postDeletion.size());
}
@Test
public void storeLoadRemoveOmemoSignedPreKeys()
throws IOException, CorruptedOmemoKeyException
{
TreeMap<Integer, T_SigPreKey> before = store.loadOmemoSignedPreKeys(alice);
assertEquals("At this stage, there must be no signed prekeys in the store.", 0, before.size());
T_IdKeyPair idp = store.generateOmemoIdentityKeyPair();
T_SigPreKey spk = store.generateOmemoSignedPreKey(idp, 125);
assertNotNull("SignedPreKey must not be null.", spk);
assertEquals("ID of signedPreKey must match.", 125, store.keyUtil().signedPreKeyIdFromKey(spk));
byte[] bytes = store.keyUtil().signedPreKeyToBytes(spk);
assertNotNull("Serialized signedPreKey must not be null", bytes);
assertNotSame("Serialized signedPreKey must not be of length 0.", 0, bytes.length);
// Stored key must equal loaded key
store.storeOmemoSignedPreKey(alice, 125, spk);
TreeMap<Integer, T_SigPreKey> after = store.loadOmemoSignedPreKeys(alice);
assertEquals("We must have exactly 1 signedPreKey now.", 1, after.size());
T_SigPreKey spk2 = after.get(after.firstKey());
assertEquals("Id of the stored signedPreKey must match the one we stored.",
125, store.keyUtil().signedPreKeyIdFromKey(spk2));
assertTrue("Serialization of stored and loaded signed preKey must equal.", Arrays.equals(
store.keyUtil().signedPreKeyToBytes(spk), store.keyUtil().signedPreKeyToBytes(spk2)));
// Random access
T_SigPreKey rspk = store.loadOmemoSignedPreKey(alice, 125);
assertTrue("Serialization of stored and randomly accessed signed preKey must equal.", Arrays.equals(
store.keyUtil().signedPreKeyToBytes(spk), store.keyUtil().signedPreKeyToBytes(rspk)));
assertNull("Non-existing signedPreKey must be returned as null.",
store.loadOmemoSignedPreKey(alice, 10000));
// Deleting
store.removeOmemoSignedPreKey(alice, 125);
assertNull("Deleted key must be returned as null.", store.loadOmemoSignedPreKey(alice, 125));
assertEquals(0, store.loadOmemoSignedPreKeys(alice).size());
}
@Test
public void loadStoreDateOfLastSignedPreKeyRenewal() throws IOException {
assertNull("The date of last signed preKey renewal must be null at this stage.",
store.getDateOfLastSignedPreKeyRenewal(alice));
Date before = new Date();
store.setDateOfLastSignedPreKeyRenewal(alice, before);
Date after = store.getDateOfLastSignedPreKeyRenewal(alice);
assertEquals("Dates must equal.", after, before);
}
@Test
public void loadStoreDateOfLastMessageReceived() throws IOException {
assertNull("The date of last message received must be null at this stage.",
store.getDateOfLastReceivedMessage(alice, bob));
Date before = new Date();
store.setDateOfLastReceivedMessage(alice, bob, before);
Date after = store.getDateOfLastReceivedMessage(alice, bob);
assertEquals("Dates must equal.", after, before);
}
@Test
public void loadStoreCachedDeviceList() throws IOException {
Integer[] active = new Integer[] {1,5,999,10};
Integer[] inactive = new Integer[] {6,7,8};
CachedDeviceList before = new CachedDeviceList(
new HashSet<>(Arrays.asList(active)),
new HashSet<>(Arrays.asList(inactive)));
assertNotNull("Loading a non-existent cached deviceList must return an empty list.",
store.loadCachedDeviceList(alice, bob.getJid()));
store.storeCachedDeviceList(alice, bob.getJid(), before);
CachedDeviceList after = store.loadCachedDeviceList(alice, bob.getJid());
assertTrue("Loaded deviceList must not be empty", after.getAllDevices().size() != 0);
assertEquals("Number of entries in active devices must match.", active.length, after.getActiveDevices().size());
assertEquals("Number of entries in inactive devices must match.", inactive.length, after.getInactiveDevices().size());
assertEquals("Number of total entries must match.", active.length + inactive.length, after.getAllDevices().size());
for (Integer a : active) {
assertTrue(after.getActiveDevices().contains(a));
assertTrue(after.getAllDevices().contains(a));
}
for (Integer i : inactive) {
assertTrue(after.getInactiveDevices().contains(i));
assertTrue(after.getAllDevices().contains(i));
}
store.storeCachedDeviceList(alice, bob.getJid(), new CachedDeviceList());
assertEquals("DeviceList must be empty after overwriting it with empty list.", 0,
store.loadCachedDeviceList(alice, bob.getJid()).getAllDevices().size());
}
@Test
public void loadAllRawSessionsReturnsEmptyMapTest() {
HashMap<Integer, T_Sess> sessions = store.loadAllRawSessionsOf(alice, bob.getJid());
assertNotNull(sessions);
assertEquals(0, sessions.size());
}
@Test
public void loadNonExistentRawSessionReturnsNullTest() {
T_Sess session = store.loadRawSession(alice, bob);
assertNull(session);
}
@Test
public void getFingerprint() throws IOException, CorruptedOmemoKeyException {
assertNull("Method must return null for a non-existent fingerprint.", store.getFingerprint(alice));
store.storeOmemoIdentityKeyPair(alice, store.generateOmemoIdentityKeyPair());
OmemoFingerprint fingerprint = store.getFingerprint(alice);
assertNotNull("fingerprint must not be null", fingerprint);
assertEquals("Fingerprint must be of length 64", 64, fingerprint.length());
store.removeOmemoIdentityKeyPair(alice); //clean up
}
// ##############################################################
// Workaround for https://github.com/junit-team/junit4/issues/671
static TemporaryFolder initStaticTemp() {
try {
return new TemporaryFolder() { { before(); } };
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
@AfterClass
public static void cleanup() throws Exception {
FileBasedOmemoStore.deleteDirectory(tmp.getRoot());
}
// ##############################################################
}

View file

@ -26,13 +26,11 @@ import java.security.NoSuchAlgorithmException;
import java.security.Security;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smackx.omemo.element.OmemoElement;
import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
import org.jivesoftware.smackx.omemo.internal.CiphertextTuple;
import org.jivesoftware.smackx.omemo.internal.ClearTextMessage;
import org.jivesoftware.smackx.omemo.internal.IdentityKeyWrapper;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation;
import org.jivesoftware.smackx.omemo.util.OmemoMessageBuilder;
@ -47,13 +45,6 @@ import org.jxmpp.jid.impl.JidCreate;
*/
public class WrapperObjectsTest {
@Test
public void identityKeyWrapperTest() {
Object pseudoKey = new Object();
IdentityKeyWrapper wrapper = new IdentityKeyWrapper(pseudoKey);
assertEquals(pseudoKey, wrapper.getIdentityKey());
}
@Test
public void ciphertextTupleTest() {
byte[] c = OmemoMessageBuilder.generateIv();
@ -69,17 +60,14 @@ public class WrapperObjectsTest {
@Test
public void clearTextMessageTest() throws Exception {
Object pseudoKey = new Object();
IdentityKeyWrapper wrapper = new IdentityKeyWrapper(pseudoKey);
BareJid senderJid = JidCreate.bareFrom("bob@server.tld");
OmemoDevice sender = new OmemoDevice(senderJid, 1234);
OmemoMessageInformation information = new OmemoMessageInformation(wrapper, sender, OmemoMessageInformation.CARBON.NONE);
OmemoMessageInformation information = new OmemoMessageInformation(null, sender, OmemoMessageInformation.CARBON.NONE);
assertTrue("OmemoInformation must state that the message is an OMEMO message.",
information.isOmemoMessage());
assertEquals(OmemoMessageInformation.CARBON.NONE, information.getCarbon());
assertEquals(sender, information.getSenderDevice());
assertEquals(wrapper, information.getSenderIdentityKey());
String body = "Decrypted Body";
Message message = new Message(senderJid, body);

View file

@ -0,0 +1,59 @@
/**
*
* Copyright the original author or authors
*
* 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.smack.omemo.util;
import java.util.HashMap;
import org.jivesoftware.smackx.omemo.OmemoFingerprint;
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
import org.jivesoftware.smackx.omemo.trust.TrustCallback;
import org.jivesoftware.smackx.omemo.trust.TrustState;
/**
* Ephemera Trust Callback used to make trust decisions in tests.
*/
public class EphemeralTrustCallback implements TrustCallback {
private final HashMap<OmemoDevice, HashMap<OmemoFingerprint, TrustState>> trustStates = new HashMap<>();
@Override
public TrustState getTrust(OmemoDevice device, OmemoFingerprint fingerprint) {
HashMap<OmemoFingerprint, TrustState> states = trustStates.get(device);
if (states != null) {
TrustState state = states.get(fingerprint);
if (state != null) {
return state;
}
}
return TrustState.undecided;
}
@Override
public void setTrust(OmemoDevice device, OmemoFingerprint fingerprint, TrustState state) {
HashMap<OmemoFingerprint, TrustState> states = trustStates.get(device);
if (states == null) {
states = new HashMap<>();
trustStates.put(device, states);
}
states.put(fingerprint, state);
}
}