1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-06-14 23:54:49 +02:00
Smack/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/CachingOmemoStore.java
Paul Schaub 1f731f6318
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

447 lines
16 KiB
Java

/**
*
* 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;
}
}