Smack/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/CachingOmemoStore.java

448 lines
16 KiB
Java
Raw Normal View History

Rework support for XEP-0384: OMEMO Encryption Changes: Rework integration tests New structure of base integration test classes bump dependency on signal-protocol-java from 2.4.0 to 2.6.2 Introduced CachingOmemoStore implementations Use CachingOmemoStore classes in integration tests Removed OmemoSession classes (replaced with more logical OmemoRatchet classes) Consequently also removed load/storeOmemoSession methods from OmemoStore Removed some clutter from KeyUtil classes Moved trust decision related code from OmemoStore to TrustCallback Require authenticated connection for many functions Add async initialization function in OmemoStore Refactor omemo test package (/java/org/jivesoftware/smack/omemo -> /java/org/jivesoftware/smackx) Remove OmemoStore method isFreshInstallation() as well as defaultDeviceId related stuff FileBasedOmemoStore: Add cleaner methods to store/load base data types (Using tryWithResource, only for future releases, once Android API gets bumped) Attempt to make OmemoManager thread safe new logic for getInstanceFor() deviceId determination OmemoManagers encrypt methods now don't throw exceptions when encryption for some devices fails. Instead message gets encrypted when possible and more information about failures gets returned alongside the message itself Added OmemoMessage class for that purpose Reworked entire OmemoService class Use safer logic for creating trust-ignoring messages (like ratchet-update messages) Restructure elements/provider in order to prepare for OMEMO namespace bumps Remove OmemoManager.regenerate() methods in favor of getInstanceFor(connection, randomDeviceId) Removed some unnecessary configuration options Prepare for support of more AES message key types Simplify session creation Where possible, avoid side effects in methods Add UntrustedOmemoIdentityException Add TrustState enum More improved tests
2018-06-13 12:29:16 +02:00
/**
*
* 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.OmemoCachedDeviceList;
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_ECPub, T_Bundle> keyUtil;
public CachingOmemoStore(OmemoKeyUtil<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_ECPub, T_Bundle> 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) {
if (wrappedStore == null) {
throw new NullPointerException("Wrapped OmemoStore MUST NOT be null!");
}
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 setDateOfLastDeviceIdPublication(OmemoDevice userDevice, OmemoDevice contactsDevice, Date date) {
getCache(userDevice).lastDeviceIdPublicationDates.put(contactsDevice, date);
if (persistent != null) {
persistent.setDateOfLastReceivedMessage(userDevice, contactsDevice, date);
}
}
@Override
public Date getDateOfLastDeviceIdPublication(OmemoDevice userDevice, OmemoDevice contactsDevice) {
Date last = getCache(userDevice).lastDeviceIdPublicationDates.get(contactsDevice);
if (last == null && persistent != null) {
last = persistent.getDateOfLastDeviceIdPublication(userDevice, contactsDevice);
if (last != null) {
getCache(userDevice).lastDeviceIdPublicationDates.put(contactsDevice, 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 OmemoCachedDeviceList loadCachedDeviceList(OmemoDevice userDevice, BareJid contact) {
OmemoCachedDeviceList 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 OmemoCachedDeviceList() : new OmemoCachedDeviceList(list);
}
@Override
public void storeCachedDeviceList(OmemoDevice userDevice,
BareJid contact,
OmemoCachedDeviceList deviceList) {
getCache(userDevice).deviceLists.put(contact, new OmemoCachedDeviceList(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_ECPub, T_Bundle>
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<OmemoDevice, Date> lastDeviceIdPublicationDates = new HashMap<>();
private final HashMap<BareJid, OmemoCachedDeviceList> deviceLists = new HashMap<>();
private Date lastRenewalDate = null;
2018-10-10 13:00:07 +02:00
private final HashMap<OmemoDevice, Integer> messageCounters = new HashMap<>();
Rework support for XEP-0384: OMEMO Encryption Changes: Rework integration tests New structure of base integration test classes bump dependency on signal-protocol-java from 2.4.0 to 2.6.2 Introduced CachingOmemoStore implementations Use CachingOmemoStore classes in integration tests Removed OmemoSession classes (replaced with more logical OmemoRatchet classes) Consequently also removed load/storeOmemoSession methods from OmemoStore Removed some clutter from KeyUtil classes Moved trust decision related code from OmemoStore to TrustCallback Require authenticated connection for many functions Add async initialization function in OmemoStore Refactor omemo test package (/java/org/jivesoftware/smack/omemo -> /java/org/jivesoftware/smackx) Remove OmemoStore method isFreshInstallation() as well as defaultDeviceId related stuff FileBasedOmemoStore: Add cleaner methods to store/load base data types (Using tryWithResource, only for future releases, once Android API gets bumped) Attempt to make OmemoManager thread safe new logic for getInstanceFor() deviceId determination OmemoManagers encrypt methods now don't throw exceptions when encryption for some devices fails. Instead message gets encrypted when possible and more information about failures gets returned alongside the message itself Added OmemoMessage class for that purpose Reworked entire OmemoService class Use safer logic for creating trust-ignoring messages (like ratchet-update messages) Restructure elements/provider in order to prepare for OMEMO namespace bumps Remove OmemoManager.regenerate() methods in favor of getInstanceFor(connection, randomDeviceId) Removed some unnecessary configuration options Prepare for support of more AES message key types Simplify session creation Where possible, avoid side effects in methods Add UntrustedOmemoIdentityException Add TrustState enum More improved tests
2018-06-13 12:29:16 +02:00
}
}