mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-25 21:42:07 +01:00
Merge pull request #273 from vanitasvitae/omemoMessageCounter
OMEMO: Add Message Counter Logic
This commit is contained in:
commit
a6776e25bb
10 changed files with 269 additions and 80 deletions
|
@ -0,0 +1,72 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2018 Paul Schaub
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smackx.omemo;
|
||||||
|
|
||||||
|
import static junit.framework.TestCase.assertEquals;
|
||||||
|
import static junit.framework.TestCase.assertFalse;
|
||||||
|
import static junit.framework.TestCase.assertTrue;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.SmackException;
|
||||||
|
import org.jivesoftware.smack.XMPPException;
|
||||||
|
import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
|
||||||
|
import org.jivesoftware.smackx.omemo.exceptions.ReadOnlyDeviceException;
|
||||||
|
import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException;
|
||||||
|
|
||||||
|
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
|
||||||
|
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
|
||||||
|
import org.igniterealtime.smack.inttest.TestNotPossibleException;
|
||||||
|
|
||||||
|
public class ReadOnlyDeviceIntegrationTest extends AbstractTwoUsersOmemoIntegrationTest {
|
||||||
|
|
||||||
|
public ReadOnlyDeviceIntegrationTest(SmackIntegrationTestEnvironment environment) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, TestNotPossibleException {
|
||||||
|
super(environment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmackIntegrationTest
|
||||||
|
public void test() throws InterruptedException, SmackException.NoResponseException, SmackException.NotLoggedInException, SmackException.NotConnectedException, CryptoFailedException, UndecidedOmemoIdentityException {
|
||||||
|
boolean prevIgnoreReadOnlyConf = OmemoConfiguration.getIgnoreReadOnlyDevices();
|
||||||
|
int prevMaxMessageCounter = OmemoConfiguration.getMaxReadOnlyMessageCount();
|
||||||
|
|
||||||
|
OmemoConfiguration.setIgnoreReadOnlyDevices(true);
|
||||||
|
// Set the maxReadOnlyMessageCount to ridiculously low threshold of 5.
|
||||||
|
// This means that Alice will be able to encrypt 5 messages for Bob, while the 6th will not be encrypted for Bob.
|
||||||
|
OmemoConfiguration.setMaxReadOnlyMessageCount(5);
|
||||||
|
|
||||||
|
// Reset counter to begin test
|
||||||
|
alice.getOmemoService().getOmemoStoreBackend().storeOmemoMessageCounter(alice.getOwnDevice(), bob.getOwnDevice(), 0);
|
||||||
|
|
||||||
|
// Since the max threshold is 5, we must be able to encrypt 5 messages for Bob.
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
assertEquals(i, alice.getOmemoService().getOmemoStoreBackend().loadOmemoMessageCounter(alice.getOwnDevice(), bob.getOwnDevice()));
|
||||||
|
OmemoMessage.Sent message = alice.encrypt(bob.getOwnJid(), "Hello World!");
|
||||||
|
assertFalse(message.getSkippedDevices().containsKey(bob.getOwnDevice()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now the message counter must be too high and Bobs device must be skipped.
|
||||||
|
OmemoMessage.Sent message = alice.encrypt(bob.getOwnJid(), "Hello World!");
|
||||||
|
Throwable exception = message.getSkippedDevices().get(bob.getOwnDevice());
|
||||||
|
assertTrue(exception instanceof ReadOnlyDeviceException);
|
||||||
|
assertEquals(bob.getOwnDevice(), ((ReadOnlyDeviceException) exception).getDevice());
|
||||||
|
|
||||||
|
// Reset the message counter
|
||||||
|
alice.getOmemoService().getOmemoStoreBackend().storeOmemoMessageCounter(alice.getOwnDevice(), bob.getOwnDevice(), 0);
|
||||||
|
|
||||||
|
// Reset the configuration to previous values
|
||||||
|
OmemoConfiguration.setMaxReadOnlyMessageCount(prevMaxMessageCounter);
|
||||||
|
OmemoConfiguration.setIgnoreReadOnlyDevices(prevIgnoreReadOnlyConf);
|
||||||
|
}
|
||||||
|
}
|
|
@ -140,6 +140,30 @@ public class CachingOmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Se
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void storeOmemoMessageCounter(OmemoDevice userDevice, OmemoDevice contactsDevice, int counter) {
|
||||||
|
getCache(userDevice).messageCounters.put(contactsDevice, counter);
|
||||||
|
if (persistent != null) {
|
||||||
|
persistent.storeOmemoMessageCounter(userDevice, contactsDevice, counter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int loadOmemoMessageCounter(OmemoDevice userDevice, OmemoDevice contactsDevice) {
|
||||||
|
Integer counter = getCache(userDevice).messageCounters.get(contactsDevice);
|
||||||
|
if (counter == null && persistent != null) {
|
||||||
|
counter = persistent.loadOmemoMessageCounter(userDevice, contactsDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (counter == null) {
|
||||||
|
counter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCache(userDevice).messageCounters.put(contactsDevice, counter);
|
||||||
|
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setDateOfLastReceivedMessage(OmemoDevice userDevice, OmemoDevice from, Date date) {
|
public void setDateOfLastReceivedMessage(OmemoDevice userDevice, OmemoDevice from, Date date) {
|
||||||
getCache(userDevice).lastMessagesDates.put(from, date);
|
getCache(userDevice).lastMessagesDates.put(from, date);
|
||||||
|
@ -442,5 +466,6 @@ public class CachingOmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Se
|
||||||
private final HashMap<OmemoDevice, Date> lastDeviceIdPublicationDates = new HashMap<>();
|
private final HashMap<OmemoDevice, Date> lastDeviceIdPublicationDates = new HashMap<>();
|
||||||
private final HashMap<BareJid, OmemoCachedDeviceList> deviceLists = new HashMap<>();
|
private final HashMap<BareJid, OmemoCachedDeviceList> deviceLists = new HashMap<>();
|
||||||
private Date lastRenewalDate = null;
|
private Date lastRenewalDate = null;
|
||||||
|
private final HashMap<OmemoDevice, Integer> messageCounters = new HashMap<>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -353,6 +354,24 @@ public abstract class FileBasedOmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigP
|
||||||
return session.exists();
|
return session.exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void storeOmemoMessageCounter(OmemoDevice userDevice, OmemoDevice contactsDevice, int counter) {
|
||||||
|
File messageCounterFile = hierarchy.getDevicesMessageCounterPath(userDevice, contactsDevice);
|
||||||
|
writeIntegers(messageCounterFile, Collections.singleton(counter));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int loadOmemoMessageCounter(OmemoDevice userDevice, OmemoDevice contactsDevice) {
|
||||||
|
File messageCounterFile = hierarchy.getDevicesMessageCounterPath(userDevice, contactsDevice);
|
||||||
|
Set<Integer> integers = readIntegers(messageCounterFile);
|
||||||
|
|
||||||
|
if (integers == null || integers.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return integers.iterator().next();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OmemoCachedDeviceList loadCachedDeviceList(OmemoDevice userDevice, BareJid contact) {
|
public OmemoCachedDeviceList loadCachedDeviceList(OmemoDevice userDevice, BareJid contact) {
|
||||||
OmemoCachedDeviceList cachedDeviceList = new OmemoCachedDeviceList();
|
OmemoCachedDeviceList cachedDeviceList = new OmemoCachedDeviceList();
|
||||||
|
@ -732,6 +751,7 @@ public abstract class FileBasedOmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigP
|
||||||
static final String SESSION = "session";
|
static final String SESSION = "session";
|
||||||
static final String DEVICE_LIST_ACTIVE = "activeDevices";
|
static final String DEVICE_LIST_ACTIVE = "activeDevices";
|
||||||
static final String DEVICE_LIST_INAVTIVE = "inactiveDevices";
|
static final String DEVICE_LIST_INAVTIVE = "inactiveDevices";
|
||||||
|
static final String MESSAGE_COUNTER = "messageCounter";
|
||||||
|
|
||||||
File basePath;
|
File basePath;
|
||||||
|
|
||||||
|
@ -815,6 +835,10 @@ public abstract class FileBasedOmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigP
|
||||||
return new File(getContactsDir(userDevice, contact), DEVICE_LIST_INAVTIVE);
|
return new File(getContactsDir(userDevice, contact), DEVICE_LIST_INAVTIVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File getDevicesMessageCounterPath(OmemoDevice userDevice, OmemoDevice otherDevice) {
|
||||||
|
return new File(getContactsDir(userDevice, otherDevice), MESSAGE_COUNTER);
|
||||||
|
}
|
||||||
|
|
||||||
private static File createFile(File f) throws IOException {
|
private static File createFile(File f) throws IOException {
|
||||||
File p = f.getParentFile();
|
File p = f.getParentFile();
|
||||||
createDirectory(p);
|
createDirectory(p);
|
||||||
|
|
|
@ -23,31 +23,56 @@ package org.jivesoftware.smackx.omemo;
|
||||||
*/
|
*/
|
||||||
public final class OmemoConfiguration {
|
public final class OmemoConfiguration {
|
||||||
|
|
||||||
|
private static boolean IGNORE_READ_ONLY_DEVICES = true;
|
||||||
|
private static int MAX_READ_ONLY_MESSAGE_COUNT = 400;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ignore own other stale devices that we did not receive a message from for a period of time.
|
* Set to true, in order to ignore read-only devices.
|
||||||
* Ignoring means do not encrypt messages for them. This helps to mitigate stale devices that threaten
|
*
|
||||||
* forward secrecy by never advancing ratchets.
|
* @param ignore ignore read-only devices
|
||||||
|
* @see <a href="example.com">Blog Post explaining the danger of read-only devices. TODO: Add URL </a>
|
||||||
*/
|
*/
|
||||||
private static boolean IGNORE_STALE_DEVICES = true;
|
public static void setIgnoreReadOnlyDevices(boolean ignore) {
|
||||||
private static int IGNORE_STALE_DEVICE_AFTER_HOURS = 24 * 7; //One week
|
IGNORE_READ_ONLY_DEVICES = ignore;
|
||||||
|
|
||||||
public static void setIgnoreStaleDevices(boolean ignore) {
|
|
||||||
IGNORE_STALE_DEVICES = ignore;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean getIgnoreStaleDevices() {
|
/**
|
||||||
return IGNORE_STALE_DEVICES;
|
* Return true, if the client should stop encrypting messages to a read-only device.
|
||||||
|
*
|
||||||
|
* @return true if read-only devices should get ignored after a certain amount of unanswered messages.
|
||||||
|
* @see <a href="example.com">Blog Post explaining the danger of read-only devices. TODO: Add URL </a>
|
||||||
|
*/
|
||||||
|
public static boolean getIgnoreReadOnlyDevices() {
|
||||||
|
return IGNORE_READ_ONLY_DEVICES;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setIgnoreStaleDevicesAfterHours(int hours) {
|
/**
|
||||||
if (hours <= 0) {
|
* Set the maximum amount of messages that the client is allowed to send to a read-only device without getting a
|
||||||
throw new IllegalArgumentException("Hours must be greater than 0.");
|
* response. Once the message counter of a device reaches that value, the client will stop encrypting messages for
|
||||||
|
* the device (given that {@link #getIgnoreReadOnlyDevices()} is true).
|
||||||
|
* This threshold is used to prevent read-only devices from weakening forward secrecy.
|
||||||
|
*
|
||||||
|
* @param maxReadOnlyMessageCount maximum number of allowed messages to a read-only device.
|
||||||
|
* @see <a href="example.com">Blog Post explaining the danger of read-only devices. TODO: Add URL </a>
|
||||||
|
*/
|
||||||
|
public static void setMaxReadOnlyMessageCount(int maxReadOnlyMessageCount) {
|
||||||
|
if (maxReadOnlyMessageCount <= 0) {
|
||||||
|
throw new IllegalArgumentException("maxReadOnlyMessageCount MUST be greater than 0.");
|
||||||
}
|
}
|
||||||
IGNORE_STALE_DEVICE_AFTER_HOURS = hours;
|
MAX_READ_ONLY_MESSAGE_COUNT = maxReadOnlyMessageCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getIgnoreStaleDevicesAfterHours() {
|
/**
|
||||||
return IGNORE_STALE_DEVICE_AFTER_HOURS;
|
* Get the maximum amount of messages that the client is allowed to send to a read-only device without getting a
|
||||||
|
* response. Once the message counter of a device reaches that value, the client will stop encrypting messages for
|
||||||
|
* the device (given that {@link #getIgnoreReadOnlyDevices()} is true).
|
||||||
|
* This threshold is used to prevent read-only devices from weakening forward secrecy.
|
||||||
|
*
|
||||||
|
* @return maximum number of allowed messages to a read-only device.
|
||||||
|
* @see <a href="example.com">Blog Post explaining the danger of read-only devices. TODO: Add URL </a>
|
||||||
|
*/
|
||||||
|
public static int getMaxReadOnlyMessageCount() {
|
||||||
|
return MAX_READ_ONLY_MESSAGE_COUNT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -43,7 +43,6 @@ import javax.crypto.NoSuchPaddingException;
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.XMPPConnection;
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
import org.jivesoftware.smack.XMPPException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.Message;
|
import org.jivesoftware.smack.packet.Message;
|
||||||
import org.jivesoftware.smack.packet.Stanza;
|
import org.jivesoftware.smack.packet.Stanza;
|
||||||
import org.jivesoftware.smack.packet.StanzaError;
|
import org.jivesoftware.smack.packet.StanzaError;
|
||||||
|
@ -62,7 +61,7 @@ import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
|
||||||
import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
|
import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
|
||||||
import org.jivesoftware.smackx.omemo.exceptions.NoIdentityKeyException;
|
import org.jivesoftware.smackx.omemo.exceptions.NoIdentityKeyException;
|
||||||
import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException;
|
import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException;
|
||||||
import org.jivesoftware.smackx.omemo.exceptions.StaleDeviceException;
|
import org.jivesoftware.smackx.omemo.exceptions.ReadOnlyDeviceException;
|
||||||
import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException;
|
import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException;
|
||||||
import org.jivesoftware.smackx.omemo.exceptions.UntrustedOmemoIdentityException;
|
import org.jivesoftware.smackx.omemo.exceptions.UntrustedOmemoIdentityException;
|
||||||
import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
|
import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
|
||||||
|
@ -323,6 +322,8 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
|
||||||
throw new AssertionError("We MUST have an identityKey for " + contactsDevice + " since we built a session." + e);
|
throw new AssertionError("We MUST have an identityKey for " + contactsDevice + " since we built a session." + e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: We don't need to update our message counter for a ratchet update message.
|
||||||
|
|
||||||
return builder.finish();
|
return builder.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,28 +387,20 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore own stale devices
|
int messageCounter = omemoStore.loadOmemoMessageCounter(userDevice, contactsDevice);
|
||||||
if (contactsDevice.getJid().equals(userDevice.getJid()) && OmemoConfiguration.getIgnoreStaleDevices()) {
|
|
||||||
|
|
||||||
Date lastMessageDate = getOmemoStoreBackend().getDateOfLastReceivedMessage(userDevice, contactsDevice);
|
// Ignore read-only devices
|
||||||
if (lastMessageDate == null) {
|
if (OmemoConfiguration.getIgnoreReadOnlyDevices()) {
|
||||||
lastMessageDate = new Date();
|
|
||||||
getOmemoStoreBackend().setDateOfLastReceivedMessage(userDevice, contactsDevice, lastMessageDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
Date lastPublicationDate = getOmemoStoreBackend().getDateOfLastDeviceIdPublication(userDevice, contactsDevice);
|
boolean readOnly = messageCounter >= OmemoConfiguration.getMaxReadOnlyMessageCount();
|
||||||
if (lastPublicationDate == null) {
|
|
||||||
lastPublicationDate = new Date();
|
|
||||||
getOmemoStoreBackend().setDateOfLastDeviceIdPublication(userDevice, contactsDevice, lastPublicationDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean stale = isStale(userDevice, contactsDevice, lastPublicationDate, OmemoConfiguration.getIgnoreStaleDevicesAfterHours());
|
if (readOnly) {
|
||||||
stale &= isStale(userDevice, contactsDevice, lastMessageDate, OmemoConfiguration.getIgnoreStaleDevicesAfterHours());
|
LOGGER.log(Level.FINE, "Device " + contactsDevice + " seems to be read-only (We sent "
|
||||||
|
+ messageCounter + " messages without getting a reply back (max allowed is " +
|
||||||
|
OmemoConfiguration.getMaxReadOnlyMessageCount() + "). Ignoring the device.");
|
||||||
|
skippedRecipients.put(contactsDevice, new ReadOnlyDeviceException(contactsDevice));
|
||||||
|
|
||||||
if (stale) {
|
// Skip this device and handle next device
|
||||||
LOGGER.log(Level.FINE, "Device " + contactsDevice + " seems to be stale (last message received "
|
|
||||||
+ lastMessageDate + ", last publication of deviceId: " + lastPublicationDate + "). Ignore it.");
|
|
||||||
skippedRecipients.put(contactsDevice, new StaleDeviceException(contactsDevice, lastMessageDate, lastPublicationDate));
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -428,6 +421,10 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
|
||||||
LOGGER.log(Level.WARNING, "Device " + contactsDevice + " is untrusted. Message is not encrypted for it.");
|
LOGGER.log(Level.WARNING, "Device " + contactsDevice + " is untrusted. Message is not encrypted for it.");
|
||||||
skippedRecipients.put(contactsDevice, e);
|
skippedRecipients.put(contactsDevice, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Increment the message counter of the device
|
||||||
|
omemoStore.storeOmemoMessageCounter(userDevice, contactsDevice,
|
||||||
|
messageCounter + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
OmemoElement element = builder.finish();
|
OmemoElement element = builder.finish();
|
||||||
|
@ -466,6 +463,9 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
|
||||||
throw new AssertionError("Cannot retrieve OmemoFingerprint of sender although decryption was successful: " + e);
|
throw new AssertionError("Cannot retrieve OmemoFingerprint of sender although decryption was successful: " + e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset the message counter.
|
||||||
|
omemoStore.storeOmemoMessageCounter(manager.getOwnDevice(), senderDevice, 0);
|
||||||
|
|
||||||
if (omemoElement.isMessageElement()) {
|
if (omemoElement.isMessageElement()) {
|
||||||
// Use symmetric message key to decrypt message payload.
|
// Use symmetric message key to decrypt message payload.
|
||||||
String plaintext = OmemoRatchet.decryptMessageElement(omemoElement, cipherAndAuthTag);
|
String plaintext = OmemoRatchet.decryptMessageElement(omemoElement, cipherAndAuthTag);
|
||||||
|
@ -1009,24 +1009,6 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
|
||||||
return removeStaleDevicesFromDeviceList(userDevice, userDevice.getJid(), deviceList, maxAgeHours);
|
return removeStaleDevicesFromDeviceList(userDevice, userDevice.getJid(), deviceList, maxAgeHours);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a copy of the contactsDeviceList, but with stale devices marked as inactive.
|
|
||||||
* Never mark our own device as stale.
|
|
||||||
* This method ignores {@link OmemoConfiguration#getIgnoreStaleDevices()}!
|
|
||||||
*
|
|
||||||
* In this case, a stale device is one of our devices, from which we haven't received an OMEMO message from
|
|
||||||
* for more than {@link OmemoConfiguration#IGNORE_STALE_DEVICE_AFTER_HOURS} hours.
|
|
||||||
*
|
|
||||||
* @param userDevice our OmemoDevice
|
|
||||||
* @param contact subjects BareJid
|
|
||||||
* @param contactsDeviceList subjects deviceList
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private OmemoCachedDeviceList ignoreStaleDevices(OmemoDevice userDevice, BareJid contact, OmemoCachedDeviceList contactsDeviceList) {
|
|
||||||
int maxAgeHours = OmemoConfiguration.getIgnoreStaleDevicesAfterHours();
|
|
||||||
return removeStaleDevicesFromDeviceList(userDevice, contact, contactsDeviceList, maxAgeHours);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a copy of the given deviceList of user contact, but with stale devices marked as inactive.
|
* Return a copy of the given deviceList of user contact, but with stale devices marked as inactive.
|
||||||
* Never mark our own device as stale. If we haven't yet received a message from a device, store the current date
|
* Never mark our own device as stale. If we haven't yet received a message from a device, store the current date
|
||||||
|
@ -1086,12 +1068,14 @@ public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Determine, whether another one of *our* devices is stale or not.
|
||||||
*
|
*
|
||||||
* @param userDevice
|
* @param userDevice our omemoDevice
|
||||||
* @param subject
|
* @param subject another one of our devices
|
||||||
* @param lastReceipt
|
* @param lastReceipt date of last received message from that device
|
||||||
* @param maxAgeHours
|
* @param maxAgeHours threshold
|
||||||
* @return
|
*
|
||||||
|
* @return staleness
|
||||||
*/
|
*/
|
||||||
static boolean isStale(OmemoDevice userDevice, OmemoDevice subject, Date lastReceipt, int maxAgeHours) {
|
static boolean isStale(OmemoDevice userDevice, OmemoDevice subject, Date lastReceipt, int maxAgeHours) {
|
||||||
if (userDevice.equals(subject)) {
|
if (userDevice.equals(subject)) {
|
||||||
|
|
|
@ -290,6 +290,27 @@ public abstract class OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_
|
||||||
*/
|
*/
|
||||||
public abstract void removeOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice);
|
public abstract void removeOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the number of messages we sent to a device since we last received a message back.
|
||||||
|
* This counter gets reset to 0 whenever we receive a message from the contacts device.
|
||||||
|
*
|
||||||
|
* @param userDevice our omemoDevice.
|
||||||
|
* @param contactsDevice device of which we want to set the message counter.
|
||||||
|
* @param counter counter value.
|
||||||
|
*/
|
||||||
|
public abstract void storeOmemoMessageCounter(OmemoDevice userDevice, OmemoDevice contactsDevice, int counter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the current value of the message counter.
|
||||||
|
* This counter represents the number of message we sent to the contactsDevice without getting a reply back.
|
||||||
|
* The default value for this counter is 0.
|
||||||
|
*
|
||||||
|
* @param userDevice our omemoDevice
|
||||||
|
* @param contactsDevice device of which we want to get the message counter.
|
||||||
|
* @return counter value.
|
||||||
|
*/
|
||||||
|
public abstract int loadOmemoMessageCounter(OmemoDevice userDevice, OmemoDevice contactsDevice);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the date of the last message that was received from a device.
|
* Set the date of the last message that was received from a device.
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2018 Paul Schaub
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smackx.omemo.exceptions;
|
||||||
|
|
||||||
|
import org.jivesoftware.smackx.omemo.OmemoConfiguration;
|
||||||
|
import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception that signals, that a device is considered read-only.
|
||||||
|
* Read-only devices are devices that receive OMEMO messages, but do not send any.
|
||||||
|
* Those devices are weakening forward secrecy. For that reason, read-only devices are ignored after n messages have
|
||||||
|
* been sent without getting a reply back.
|
||||||
|
*/
|
||||||
|
public class ReadOnlyDeviceException extends Exception {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final OmemoDevice device;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
* We do not need to hand over the current value of the message counter, as that value will always be equal to
|
||||||
|
* {@link OmemoConfiguration#getMaxReadOnlyMessageCount()}. Therefore providing the {@link OmemoDevice} should be
|
||||||
|
* enough.
|
||||||
|
*
|
||||||
|
* @param device device which is considered read-only.
|
||||||
|
*/
|
||||||
|
public ReadOnlyDeviceException(OmemoDevice device) {
|
||||||
|
this.device = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the device in question.
|
||||||
|
* @return device
|
||||||
|
*/
|
||||||
|
public OmemoDevice getDevice() {
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,20 +52,6 @@ public class OmemoConfigurationTest {
|
||||||
// Expected.
|
// Expected.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore stale device
|
|
||||||
OmemoConfiguration.setIgnoreStaleDevices(false);
|
|
||||||
assertEquals(false, OmemoConfiguration.getIgnoreStaleDevices());
|
|
||||||
OmemoConfiguration.setIgnoreStaleDevices(true);
|
|
||||||
assertEquals(true, OmemoConfiguration.getIgnoreStaleDevices());
|
|
||||||
OmemoConfiguration.setIgnoreStaleDevicesAfterHours(44);
|
|
||||||
assertEquals(44, OmemoConfiguration.getIgnoreStaleDevicesAfterHours());
|
|
||||||
try {
|
|
||||||
OmemoConfiguration.setIgnoreStaleDevicesAfterHours(-5);
|
|
||||||
TestCase.fail("OmemoConfiguration.setIgnoreStaleDevicesAfterHours should not accept values <= 0.");
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
// Expected
|
|
||||||
}
|
|
||||||
|
|
||||||
// Renew signedPreKeys
|
// Renew signedPreKeys
|
||||||
OmemoConfiguration.setRenewOldSignedPreKeys(false);
|
OmemoConfiguration.setRenewOldSignedPreKeys(false);
|
||||||
assertEquals(false, OmemoConfiguration.getRenewOldSignedPreKeys());
|
assertEquals(false, OmemoConfiguration.getRenewOldSignedPreKeys());
|
||||||
|
|
|
@ -32,7 +32,6 @@ import org.jxmpp.stringprep.XmppStringprepException;
|
||||||
public class OmemoServiceTest extends SmackTestSuite {
|
public class OmemoServiceTest extends SmackTestSuite {
|
||||||
|
|
||||||
private static final long ONE_HOUR = 1000L * 60 * 60;
|
private static final long ONE_HOUR = 1000L * 60 * 60;
|
||||||
private static final int IGNORE_STALE = OmemoConfiguration.getIgnoreStaleDevicesAfterHours();
|
|
||||||
private static final int DELETE_STALE = OmemoConfiguration.getDeleteStaleDevicesAfterHours();
|
private static final int DELETE_STALE = OmemoConfiguration.getDeleteStaleDevicesAfterHours();
|
||||||
|
|
||||||
@Test(expected = IllegalStateException.class)
|
@Test(expected = IllegalStateException.class)
|
||||||
|
@ -55,15 +54,9 @@ public class OmemoServiceTest extends SmackTestSuite {
|
||||||
OmemoDevice other = new OmemoDevice(JidCreate.bareFrom("bob@builder.tv"), 444);
|
OmemoDevice other = new OmemoDevice(JidCreate.bareFrom("bob@builder.tv"), 444);
|
||||||
|
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
Date ignoreMe = new Date(now.getTime() - ((IGNORE_STALE + 1) * ONE_HOUR));
|
|
||||||
Date deleteMe = new Date(now.getTime() - ((DELETE_STALE + 1) * ONE_HOUR));
|
Date deleteMe = new Date(now.getTime() - ((DELETE_STALE + 1) * ONE_HOUR));
|
||||||
Date imFine = new Date(now.getTime() - ONE_HOUR);
|
|
||||||
|
|
||||||
// One hour "old" devices are (probably) not not stale
|
|
||||||
assertFalse(OmemoService.isStale(user, other, imFine, IGNORE_STALE));
|
|
||||||
|
|
||||||
// Devices one hour "older" than max ages are stale
|
// Devices one hour "older" than max ages are stale
|
||||||
assertTrue(OmemoService.isStale(user, other, ignoreMe, IGNORE_STALE));
|
|
||||||
assertTrue(OmemoService.isStale(user, other, deleteMe, DELETE_STALE));
|
assertTrue(OmemoService.isStale(user, other, deleteMe, DELETE_STALE));
|
||||||
|
|
||||||
// Own device is never stale, no matter how old
|
// Own device is never stale, no matter how old
|
||||||
|
|
|
@ -314,6 +314,13 @@ public abstract class OmemoStoreTest<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey
|
||||||
assertNull(session);
|
assertNull(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void loadStoreMessageCounterTest() {
|
||||||
|
assertEquals(0, store.loadOmemoMessageCounter(alice, bob));
|
||||||
|
store.storeOmemoMessageCounter(alice, bob, 20);
|
||||||
|
assertEquals(20, store.loadOmemoMessageCounter(alice, bob));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getFingerprint() throws IOException, CorruptedOmemoKeyException {
|
public void getFingerprint() throws IOException, CorruptedOmemoKeyException {
|
||||||
assertNull("Method must return null for a non-existent fingerprint.", store.getFingerprint(alice));
|
assertNull("Method must return null for a non-existent fingerprint.", store.getFingerprint(alice));
|
||||||
|
|
Loading…
Reference in a new issue