001/**
002 *
003 * Copyright 2017 Paul Schaub, 2019 Florian Schmaus
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smackx.omemo;
018
019import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYLENGTH;
020import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYTYPE;
021
022import java.io.IOException;
023import java.security.InvalidAlgorithmParameterException;
024import java.security.InvalidKeyException;
025import java.security.NoSuchAlgorithmException;
026import java.util.ArrayList;
027import java.util.Collection;
028import java.util.Collections;
029import java.util.Date;
030import java.util.HashMap;
031import java.util.HashSet;
032import java.util.List;
033import java.util.Random;
034import java.util.Set;
035import java.util.logging.Level;
036import java.util.logging.Logger;
037
038import javax.crypto.BadPaddingException;
039import javax.crypto.IllegalBlockSizeException;
040import javax.crypto.NoSuchPaddingException;
041
042import org.jivesoftware.smack.SmackException;
043import org.jivesoftware.smack.XMPPConnection;
044import org.jivesoftware.smack.XMPPException;
045import org.jivesoftware.smack.packet.Message;
046import org.jivesoftware.smack.packet.Stanza;
047import org.jivesoftware.smack.packet.StanzaError;
048
049import org.jivesoftware.smackx.carbons.packet.CarbonExtension;
050import org.jivesoftware.smackx.mam.MamManager;
051import org.jivesoftware.smackx.muc.MultiUserChat;
052import org.jivesoftware.smackx.muc.MultiUserChatManager;
053import org.jivesoftware.smackx.muc.Occupant;
054import org.jivesoftware.smackx.omemo.element.OmemoBundleElement;
055import org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement;
056import org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement_VAxolotl;
057import org.jivesoftware.smackx.omemo.element.OmemoElement;
058import org.jivesoftware.smackx.omemo.element.OmemoElement_VAxolotl;
059import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException;
060import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
061import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
062import org.jivesoftware.smackx.omemo.exceptions.NoIdentityKeyException;
063import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException;
064import org.jivesoftware.smackx.omemo.exceptions.ReadOnlyDeviceException;
065import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException;
066import org.jivesoftware.smackx.omemo.exceptions.UntrustedOmemoIdentityException;
067import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
068import org.jivesoftware.smackx.omemo.internal.OmemoCachedDeviceList;
069import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
070import org.jivesoftware.smackx.omemo.internal.listener.OmemoCarbonCopyStanzaReceivedListener;
071import org.jivesoftware.smackx.omemo.internal.listener.OmemoMessageStanzaReceivedListener;
072import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint;
073import org.jivesoftware.smackx.omemo.trust.OmemoTrustCallback;
074import org.jivesoftware.smackx.omemo.trust.TrustState;
075import org.jivesoftware.smackx.omemo.util.MessageOrOmemoMessage;
076import org.jivesoftware.smackx.omemo.util.OmemoConstants;
077import org.jivesoftware.smackx.omemo.util.OmemoMessageBuilder;
078import org.jivesoftware.smackx.pep.PepManager;
079import org.jivesoftware.smackx.pubsub.LeafNode;
080import org.jivesoftware.smackx.pubsub.PayloadItem;
081import org.jivesoftware.smackx.pubsub.PubSubException;
082import org.jivesoftware.smackx.pubsub.PubSubException.NotALeafNodeException;
083import org.jivesoftware.smackx.pubsub.PubSubManager;
084
085import org.jxmpp.jid.BareJid;
086import org.jxmpp.jid.EntityBareJid;
087import org.jxmpp.jid.Jid;
088
089/**
090 * This class contains OMEMO related logic and registers listeners etc.
091 *
092 * @param <T_IdKeyPair> IdentityKeyPair class
093 * @param <T_IdKey>     IdentityKey class
094 * @param <T_PreKey>    PreKey class
095 * @param <T_SigPreKey> SignedPreKey class
096 * @param <T_Sess>      Session class
097 * @param <T_Addr>      Address class
098 * @param <T_ECPub>     Elliptic Curve PublicKey class
099 * @param <T_Bundle>    Bundle class
100 * @param <T_Ciph>      Cipher class
101 *
102 * @author Paul Schaub
103 */
104public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
105        implements OmemoCarbonCopyStanzaReceivedListener, OmemoMessageStanzaReceivedListener {
106
107    protected static final Logger LOGGER = Logger.getLogger(OmemoService.class.getName());
108
109    private static final long MILLIS_PER_HOUR = 1000L * 60 * 60;
110
111    private static OmemoService<?, ?, ?, ?, ?, ?, ?, ?, ?> INSTANCE;
112
113    private OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore;
114    private final HashMap<OmemoManager, OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>> omemoRatchets = new HashMap<>();
115
116    protected OmemoService() {
117
118    }
119
120    /**
121     * Return the singleton instance of this class. When no instance is set, throw an IllegalStateException instead.
122     *
123     * @return instance.
124     */
125    public static OmemoService<?, ?, ?, ?, ?, ?, ?, ?, ?> getInstance() {
126        if (INSTANCE == null) {
127            throw new IllegalStateException("No OmemoService registered");
128        }
129        return INSTANCE;
130    }
131
132    /**
133     * Set singleton instance. Throws an IllegalStateException, if there is already a service set as instance.
134     *
135     * @param omemoService instance
136     */
137    protected static void setInstance(OmemoService<?, ?, ?, ?, ?, ?, ?, ?, ?> omemoService) {
138        if (INSTANCE != null) {
139            throw new IllegalStateException("An OmemoService is already registered");
140        }
141        INSTANCE = omemoService;
142    }
143
144    /**
145     * Returns true, if an instance of the service singleton is set. Otherwise return false.
146     *
147     * @return true, if instance is not null.
148     */
149    public static boolean isServiceRegistered() {
150        return INSTANCE != null;
151    }
152
153    /**
154     * Return the used omemoStore backend.
155     * If there is no store backend set yet, set the default one (typically a file-based one).
156     *
157     * @return omemoStore backend
158     */
159    public OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
160    getOmemoStoreBackend() {
161        if (omemoStore == null) {
162            omemoStore = createDefaultOmemoStoreBackend();
163        }
164        return omemoStore;
165    }
166
167    /**
168     * Set an omemoStore as backend. Throws an IllegalStateException, if there is already a backend set.
169     *
170     * @param omemoStore store.
171     */
172    @SuppressWarnings("unused")
173    public void setOmemoStoreBackend(
174            OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore) {
175        if (this.omemoStore != null) {
176            throw new IllegalStateException("An OmemoStore backend has already been set.");
177        }
178        this.omemoStore = omemoStore;
179    }
180
181    /**
182     * Create a default OmemoStore object.
183     *
184     * @return default omemoStore.
185     */
186    protected abstract OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
187    createDefaultOmemoStoreBackend();
188
189    /**
190     * Return a new instance of the OMEMO ratchet.
191     * The ratchet is internally used to encrypt/decrypt message keys.
192     *
193     * @param manager OmemoManager
194     * @param store OmemoStore
195     * @return instance of the OmemoRatchet
196     */
197    protected abstract OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
198    instantiateOmemoRatchet(OmemoManager manager,
199                            OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> store);
200
201    /**
202     * Return the deposited instance of the OmemoRatchet for the given manager.
203     * If there is none yet, create a new one, deposit it and return it.
204     *
205     * @param manager OmemoManager we want to have the ratchet for.
206     * @return OmemoRatchet instance
207     */
208    protected OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
209    getOmemoRatchet(OmemoManager manager) {
210        OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
211                omemoRatchet = omemoRatchets.get(manager);
212        if (omemoRatchet == null) {
213            omemoRatchet = instantiateOmemoRatchet(manager, omemoStore);
214            omemoRatchets.put(manager, omemoRatchet);
215        }
216        return omemoRatchet;
217    }
218
219    /**
220     * Instantiate and deposit a Ratchet for the given OmemoManager.
221     *
222     * @param manager manager.
223     */
224    void registerRatchetForManager(OmemoManager manager) {
225        omemoRatchets.put(manager, instantiateOmemoRatchet(manager, getOmemoStoreBackend()));
226    }
227
228    /**
229     * Initialize OMEMO functionality for the given {@link OmemoManager}.
230     *
231     * @param managerGuard OmemoManager we'd like to initialize.
232     *
233     * @throws InterruptedException if the calling thread was interrupted.
234     * @throws CorruptedOmemoKeyException if the OMEMO key is corrupted.
235     * @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
236     * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
237     * @throws SmackException.NoResponseException if there was no response from the remote entity.
238     * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
239     * @throws IOException if an I/O error occurred.
240     */
241    void init(OmemoManager.LoggedInOmemoManager managerGuard)
242            throws InterruptedException, CorruptedOmemoKeyException, XMPPException.XMPPErrorException,
243            SmackException.NotConnectedException, SmackException.NoResponseException,
244            PubSubException.NotALeafNodeException, IOException {
245
246        OmemoManager manager = managerGuard.get();
247        OmemoDevice userDevice = manager.getOwnDevice();
248
249        // Create new keys if necessary and publish to the server.
250        getOmemoStoreBackend().replenishKeys(userDevice);
251
252        // Rotate signed preKey if necessary.
253        if (shouldRotateSignedPreKey(userDevice)) {
254            getOmemoStoreBackend().changeSignedPreKey(userDevice);
255        }
256
257        // Pack and publish bundle
258        OmemoBundleElement bundle = getOmemoStoreBackend().packOmemoBundle(userDevice);
259        publishBundle(manager.getConnection(), userDevice, bundle);
260
261        // Fetch device list and republish deviceId if necessary
262        refreshAndRepublishDeviceList(manager.getConnection(), userDevice);
263    }
264
265    /**
266     * Create an empty OMEMO message, which is used to forward the ratchet of the recipient.
267     * This message type is typically used to create stable sessions.
268     * Note that trust decisions are ignored for the creation of this message.
269     *
270     * @param managerGuard Logged in OmemoManager
271     * @param contactsDevice OmemoDevice of the contact
272     * @return ratchet update message
273     *
274     * @throws NoSuchAlgorithmException if AES algorithms are not supported on this system.
275     * @throws InterruptedException if the calling thread was interrupted.
276     * @throws SmackException.NoResponseException if there was no response from the remote entity.
277     * @throws CorruptedOmemoKeyException if our IdentityKeyPair is corrupted.
278     * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
279     * @throws CannotEstablishOmemoSessionException if session negotiation fails.
280     * @throws IOException if an I/O error occurred.
281     */
282    OmemoElement createRatchetUpdateElement(OmemoManager.LoggedInOmemoManager managerGuard,
283                                            OmemoDevice contactsDevice)
284            throws InterruptedException, SmackException.NoResponseException, CorruptedOmemoKeyException,
285            SmackException.NotConnectedException, CannotEstablishOmemoSessionException, NoSuchAlgorithmException,
286            CryptoFailedException, IOException {
287
288        OmemoManager manager = managerGuard.get();
289        OmemoDevice userDevice = manager.getOwnDevice();
290
291        if (contactsDevice.equals(userDevice)) {
292            throw new IllegalArgumentException("\"Thou shall not update thy own ratchet!\" - William Shakespeare");
293        }
294
295        // Establish session if necessary
296        if (!hasSession(userDevice, contactsDevice)) {
297            buildFreshSessionWithDevice(manager.getConnection(), userDevice, contactsDevice);
298        }
299
300        // Generate fresh AES key and IV
301        byte[] messageKey = OmemoMessageBuilder.generateKey(KEYTYPE, KEYLENGTH);
302        byte[] iv = OmemoMessageBuilder.generateIv();
303
304        // Create message builder
305        OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> builder;
306        try {
307            builder = new OmemoMessageBuilder<>(userDevice, gullibleTrustCallback, getOmemoRatchet(manager),
308                    messageKey, iv, null);
309        } catch (InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException e) {
310            throw new CryptoFailedException(e);
311        }
312
313        // Add recipient
314        try {
315            builder.addRecipient(contactsDevice);
316        } catch (UndecidedOmemoIdentityException | UntrustedOmemoIdentityException e) {
317            throw new AssertionError("Gullible Trust Callback reported undecided or untrusted device, " +
318                    "even though it MUST NOT do that.");
319        } catch (NoIdentityKeyException e) {
320            throw new AssertionError("We MUST have an identityKey for " + contactsDevice + " since we built a session." + e);
321        }
322
323        // Note: We don't need to update our message counter for a ratchet update message.
324
325        return builder.finish();
326    }
327
328    /**
329     * Encrypt a message with a messageKey and an IV and create an OmemoMessage from it.
330     *
331     * @param managerGuard authenticated OmemoManager
332     * @param contactsDevices set of recipient OmemoDevices
333     * @param messageKey AES key to encrypt the message
334     * @param iv iv to be used with the messageKey
335     * @return OmemoMessage object which contains the OmemoElement and some information.
336     *
337     * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
338     * @throws InterruptedException if the calling thread was interrupted.
339     * @throws SmackException.NoResponseException if there was no response from the remote entity.
340     * @throws UndecidedOmemoIdentityException if the list of recipient devices contains undecided devices
341     * @throws CryptoFailedException if we are lacking some crypto primitives
342     * @throws IOException if an I/O error occurred.
343     */
344    private OmemoMessage.Sent encrypt(OmemoManager.LoggedInOmemoManager managerGuard,
345                                      Set<OmemoDevice> contactsDevices,
346                                      byte[] messageKey,
347                                      byte[] iv,
348                                      String message)
349            throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException,
350            UndecidedOmemoIdentityException, CryptoFailedException, IOException {
351
352        OmemoManager manager = managerGuard.get();
353        OmemoDevice userDevice = manager.getOwnDevice();
354
355        // Do not encrypt for our own device.
356        removeOurDevice(userDevice, contactsDevices);
357
358        buildMissingSessionsWithDevices(manager.getConnection(), userDevice, contactsDevices);
359
360        Set<OmemoDevice> undecidedDevices = getUndecidedDevices(userDevice, manager.getTrustCallback(), contactsDevices);
361        if (!undecidedDevices.isEmpty()) {
362            throw new UndecidedOmemoIdentityException(undecidedDevices);
363        }
364
365        // Keep track of skipped devices
366        HashMap<OmemoDevice, Throwable> skippedRecipients = new HashMap<>();
367
368        OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> builder;
369        try {
370            builder = new OmemoMessageBuilder<>(
371                    userDevice, manager.getTrustCallback(), getOmemoRatchet(managerGuard.get()), messageKey, iv, message);
372        } catch (BadPaddingException | IllegalBlockSizeException |
373                NoSuchPaddingException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException e) {
374            throw new CryptoFailedException(e);
375        }
376
377        for (OmemoDevice contactsDevice : contactsDevices) {
378            // Build missing sessions
379            if (!hasSession(userDevice, contactsDevice)) {
380                try {
381                    buildFreshSessionWithDevice(manager.getConnection(), userDevice, contactsDevice);
382                } catch (CorruptedOmemoKeyException | CannotEstablishOmemoSessionException e) {
383                    LOGGER.log(Level.WARNING, "Could not build session with " + contactsDevice + ".", e);
384                    skippedRecipients.put(contactsDevice, e);
385                    continue;
386                }
387            }
388
389            int messageCounter = omemoStore.loadOmemoMessageCounter(userDevice, contactsDevice);
390
391            // Ignore read-only devices
392            if (OmemoConfiguration.getIgnoreReadOnlyDevices()) {
393
394                boolean readOnly = messageCounter >= OmemoConfiguration.getMaxReadOnlyMessageCount();
395
396                if (readOnly) {
397                    LOGGER.log(Level.FINE, "Device " + contactsDevice + " seems to be read-only (We sent "
398                            + messageCounter + " messages without getting a reply back (max allowed is " +
399                            OmemoConfiguration.getMaxReadOnlyMessageCount() + "). Ignoring the device.");
400                    skippedRecipients.put(contactsDevice, new ReadOnlyDeviceException(contactsDevice));
401
402                    // Skip this device and handle next device
403                    continue;
404                }
405            }
406
407            // Add recipients
408            try {
409                builder.addRecipient(contactsDevice);
410            }
411            catch (NoIdentityKeyException | CorruptedOmemoKeyException e) {
412                LOGGER.log(Level.WARNING, "Encryption failed for device " + contactsDevice + ".", e);
413                skippedRecipients.put(contactsDevice, e);
414            }
415            catch (UndecidedOmemoIdentityException e) {
416                throw new AssertionError("Recipients device seems to be undecided, even though we should have thrown" +
417                        " an exception earlier in that case. " + e);
418            }
419            catch (UntrustedOmemoIdentityException e) {
420                LOGGER.log(Level.WARNING, "Device " + contactsDevice + " is untrusted. Message is not encrypted for it.");
421                skippedRecipients.put(contactsDevice, e);
422            }
423
424            // Increment the message counter of the device
425            omemoStore.storeOmemoMessageCounter(userDevice, contactsDevice,
426                    messageCounter + 1);
427        }
428
429        OmemoElement element = builder.finish();
430
431        return new OmemoMessage.Sent(element, messageKey, iv, contactsDevices, skippedRecipients);
432    }
433
434    /**
435     * Decrypt an OMEMO message.
436     *
437     * @param managerGuard authenticated OmemoManager.
438     * @param senderJid BareJid of the sender.
439     * @param omemoElement omemoElement.
440     * @return decrypted OmemoMessage object.
441     *
442     * @throws CorruptedOmemoKeyException if the identityKey of the sender is damaged.
443     * @throws CryptoFailedException if decryption fails.
444     * @throws NoRawSessionException if we have no session with the device and it sent a normal (non-preKey) message.
445     * @throws IOException if an I/O error occurred.
446     */
447    OmemoMessage.Received decryptMessage(OmemoManager.LoggedInOmemoManager managerGuard,
448                                         BareJid senderJid,
449                                         OmemoElement omemoElement)
450            throws CorruptedOmemoKeyException, CryptoFailedException, NoRawSessionException, IOException {
451
452        OmemoManager manager = managerGuard.get();
453        int senderId = omemoElement.getHeader().getSid();
454        OmemoDevice senderDevice = new OmemoDevice(senderJid, senderId);
455
456        CipherAndAuthTag cipherAndAuthTag = getOmemoRatchet(manager)
457                .retrieveMessageKeyAndAuthTag(senderDevice, omemoElement);
458
459        // Retrieve senders fingerprint.
460        OmemoFingerprint senderFingerprint;
461        try {
462            senderFingerprint = getOmemoStoreBackend().getFingerprint(manager.getOwnDevice(), senderDevice);
463        } catch (NoIdentityKeyException e) {
464            throw new AssertionError("Cannot retrieve OmemoFingerprint of sender although decryption was successful: " + e);
465        }
466
467        // Reset the message counter.
468        omemoStore.storeOmemoMessageCounter(manager.getOwnDevice(), senderDevice, 0);
469
470        if (omemoElement.isMessageElement()) {
471            // Use symmetric message key to decrypt message payload.
472            String plaintext = OmemoRatchet.decryptMessageElement(omemoElement, cipherAndAuthTag);
473
474            return new OmemoMessage.Received(omemoElement, cipherAndAuthTag.getKey(), cipherAndAuthTag.getIv(),
475                    plaintext, senderFingerprint, senderDevice, cipherAndAuthTag.wasPreKeyEncrypted());
476
477        } else {
478            // KeyTransportMessages don't require decryption of the payload.
479            return new OmemoMessage.Received(omemoElement, cipherAndAuthTag.getKey(), cipherAndAuthTag.getIv(),
480                    null, senderFingerprint, senderDevice, cipherAndAuthTag.wasPreKeyEncrypted());
481        }
482    }
483
484    /**
485     * Create an OMEMO KeyTransportElement.
486     *
487     * @see <a href="https://xmpp.org/extensions/xep-0384.html#usecases-keysend">XEP-0384: Sending a key</a>.
488     *
489     * @param managerGuard Initialized OmemoManager.
490     * @param contactsDevices set of recipient devices.
491     * @param key AES-Key to be transported.
492     * @param iv initialization vector to be used with the key.
493     *
494     * @return a new key transport element
495     *
496     * @throws InterruptedException if the calling thread was interrupted.
497     * @throws UndecidedOmemoIdentityException if the list of recipients contains an undecided device
498     * @throws CryptoFailedException if we are lacking some cryptographic algorithms
499     * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
500     * @throws SmackException.NoResponseException if there was no response from the remote entity.
501     * @throws IOException if an I/O error occurred.
502     */
503    OmemoMessage.Sent createKeyTransportElement(OmemoManager.LoggedInOmemoManager managerGuard,
504                                                Set<OmemoDevice> contactsDevices,
505                                                byte[] key,
506                                                byte[] iv)
507            throws InterruptedException, UndecidedOmemoIdentityException, CryptoFailedException,
508            SmackException.NotConnectedException, SmackException.NoResponseException, IOException {
509        return encrypt(managerGuard, contactsDevices, key, iv, null);
510    }
511
512    /**
513     * Create an OmemoMessage.
514     *
515     * @param managerGuard initialized OmemoManager
516     * @param contactsDevices set of recipient devices
517     * @param message message we want to send
518     * @return encrypted OmemoMessage
519     *
520     * @throws InterruptedException if the calling thread was interrupted.
521     * @throws UndecidedOmemoIdentityException if the list of recipient devices contains an undecided device.
522     * @throws CryptoFailedException if we are lacking some cryptographic algorithms
523     * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
524     * @throws SmackException.NoResponseException if there was no response from the remote entity.
525     * @throws IOException if an I/O error occurred.
526     */
527    OmemoMessage.Sent createOmemoMessage(OmemoManager.LoggedInOmemoManager managerGuard,
528                                         Set<OmemoDevice> contactsDevices,
529                                         String message)
530            throws InterruptedException, UndecidedOmemoIdentityException, CryptoFailedException,
531            SmackException.NotConnectedException, SmackException.NoResponseException, IOException {
532
533        byte[] key, iv;
534        iv = OmemoMessageBuilder.generateIv();
535
536        try {
537            key = OmemoMessageBuilder.generateKey(KEYTYPE, KEYLENGTH);
538        } catch (NoSuchAlgorithmException e) {
539            throw new CryptoFailedException(e);
540        }
541
542        return encrypt(managerGuard, contactsDevices, key, iv, message);
543    }
544
545    /**
546     * Retrieve a users OMEMO bundle.
547     *
548     * @param connection authenticated XMPP connection.
549     * @param contactsDevice device of which we want to retrieve the bundle.
550     * @return OmemoBundle of the device or null, if it doesn't exist.
551     *
552     * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
553     * @throws InterruptedException if the calling thread was interrupted.
554     * @throws SmackException.NoResponseException if there was no response from the remote entity.
555     * @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
556     * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
557     * @throws PubSubException.NotAPubSubNodeException if a involved node is not a PubSub node.
558     */
559    private static OmemoBundleElement fetchBundle(XMPPConnection connection,
560                                                  OmemoDevice contactsDevice)
561            throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException,
562            XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException,
563            PubSubException.NotAPubSubNodeException {
564
565        PubSubManager pm = PubSubManager.getInstanceFor(connection, contactsDevice.getJid());
566        LeafNode node = pm.getLeafNode(contactsDevice.getBundleNodeName());
567
568        if (node == null) {
569            return null;
570        }
571
572        List<PayloadItem<OmemoBundleElement>> bundleItems = node.getItems();
573        if (bundleItems.isEmpty()) {
574            return null;
575        }
576
577        return bundleItems.get(bundleItems.size() - 1).getPayload();
578    }
579
580    /**
581     * Publish the given OMEMO bundle to the server using PubSub.
582     *
583     * @param connection our connection.
584     * @param userDevice our device
585     * @param bundle the bundle we want to publish
586     *
587     * @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
588     * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
589     * @throws InterruptedException if the calling thread was interrupted.
590     * @throws SmackException.NoResponseException if there was no response from the remote entity.
591     * @throws NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
592     */
593    static void publishBundle(XMPPConnection connection, OmemoDevice userDevice, OmemoBundleElement bundle)
594            throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
595            SmackException.NoResponseException, NotALeafNodeException {
596        PepManager pm = PepManager.getInstanceFor(connection);
597        pm.publish(userDevice.getBundleNodeName(), new PayloadItem<>(bundle));
598    }
599
600    /**
601     * Retrieve the OMEMO device list of a contact.
602     *
603     * @param connection authenticated XMPP connection.
604     * @param contact BareJid of the contact of which we want to retrieve the device list from.
605     * @return device list
606     *
607     * @throws InterruptedException if the calling thread was interrupted.
608     * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
609     * @throws SmackException.NoResponseException if there was no response from the remote entity.
610     * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
611     * @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
612     * @throws PubSubException.NotAPubSubNodeException if a involved node is not a PubSub node.
613     */
614    private static OmemoDeviceListElement fetchDeviceList(XMPPConnection connection, BareJid contact)
615            throws InterruptedException, PubSubException.NotALeafNodeException, SmackException.NoResponseException,
616            SmackException.NotConnectedException, XMPPException.XMPPErrorException,
617            PubSubException.NotAPubSubNodeException {
618
619        PubSubManager pm = PubSubManager.getInstanceFor(connection, contact);
620        String nodeName = OmemoConstants.PEP_NODE_DEVICE_LIST;
621        LeafNode node = pm.getLeafNode(nodeName);
622
623        if (node == null) {
624            return null;
625        }
626
627        List<PayloadItem<OmemoDeviceListElement>> items = node.getItems();
628        if (items.isEmpty()) {
629            return null;
630        }
631
632        return items.get(items.size() - 1).getPayload();
633    }
634
635    /**
636     * Publish the given device list to the server.
637     *
638     * @param connection authenticated XMPP connection.
639     * @param deviceList users deviceList.
640     *
641     * @throws InterruptedException if the calling thread was interrupted.
642     * @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
643     * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
644     * @throws SmackException.NoResponseException if there was no response from the remote entity.
645     * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
646     */
647    static void publishDeviceList(XMPPConnection connection, OmemoDeviceListElement deviceList)
648            throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException,
649            SmackException.NoResponseException, NotALeafNodeException {
650        PepManager pm = PepManager.getInstanceFor(connection);
651        pm.publish(OmemoConstants.PEP_NODE_DEVICE_LIST, new PayloadItem<>(deviceList));
652    }
653
654    /**
655     * Refresh our own device list and publish it to the server.
656     *
657     * @param connection XMPPConnection
658     * @param userDevice our OMEMO device
659     *
660     * @throws InterruptedException if the calling thread was interrupted.
661     * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
662     * @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
663     * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
664     * @throws SmackException.NoResponseException if there was no response from the remote entity.
665     * @throws IOException if an I/O error occurred.
666     */
667    private void refreshAndRepublishDeviceList(XMPPConnection connection, OmemoDevice userDevice)
668            throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
669            SmackException.NotConnectedException, SmackException.NoResponseException, IOException {
670
671        // refreshOmemoDeviceList;
672        OmemoDeviceListElement publishedList;
673
674        try {
675            publishedList = fetchDeviceList(connection, userDevice.getJid());
676        } catch (PubSubException.NotAPubSubNodeException e) {
677            // Node is not a PubSub node. This might happen on some ejabberd servers.
678            publishedList = null;
679        } catch (XMPPException.XMPPErrorException e) {
680            if (e.getStanzaError().getCondition() == StanzaError.Condition.item_not_found) {
681                // Items not found -> items do not exist
682                publishedList = null;
683            } else {
684                // Some other error -> throw
685                throw e;
686            }
687        }
688        if (publishedList == null) {
689            publishedList = new OmemoDeviceListElement_VAxolotl(Collections.<Integer>emptySet());
690        }
691
692        getOmemoStoreBackend().mergeCachedDeviceList(userDevice, userDevice.getJid(), publishedList);
693
694        OmemoCachedDeviceList cachedList = cleanUpDeviceList(userDevice);
695
696        // Republish our deviceId if it is missing from the published list.
697        if (!publishedList.getDeviceIds().equals(cachedList.getActiveDevices())) {
698            publishDeviceList(connection, new OmemoDeviceListElement_VAxolotl(cachedList));
699        }
700    }
701
702    /**
703     * Add our load the deviceList of the user from cache, delete stale devices if needed, add the users device
704     * back if necessary, store the refurbished list in cache and return it.
705     *
706     * @param userDevice our own OMEMO device
707     * @return cleaned device list
708     *
709     * @throws IOException if an I/O error occurred.
710     */
711    OmemoCachedDeviceList cleanUpDeviceList(OmemoDevice userDevice) throws IOException {
712        OmemoCachedDeviceList cachedDeviceList;
713
714        // Delete stale devices if allowed and necessary
715        if (OmemoConfiguration.getDeleteStaleDevices()) {
716            cachedDeviceList = deleteStaleDevices(userDevice);
717        } else {
718            cachedDeviceList = getOmemoStoreBackend().loadCachedDeviceList(userDevice);
719        }
720
721
722        // Add back our device if necessary
723        if (!cachedDeviceList.getActiveDevices().contains(userDevice.getDeviceId())) {
724            cachedDeviceList.addDevice(userDevice.getDeviceId());
725        }
726
727        getOmemoStoreBackend().storeCachedDeviceList(userDevice, userDevice.getJid(), cachedDeviceList);
728        return cachedDeviceList;
729    }
730
731    /**
732     * Refresh and merge device list of contact.
733     *
734     * @param connection authenticated XMPP connection
735     * @param userDevice our OmemoDevice
736     * @param contact contact we want to fetch the deviceList from
737     * @return cached device list after refresh.
738     *
739     * @throws InterruptedException if the calling thread was interrupted.
740     * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
741     * @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
742     * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
743     * @throws SmackException.NoResponseException if there was no response from the remote entity.
744     * @throws IOException if an I/O error occurred.
745     */
746    OmemoCachedDeviceList refreshDeviceList(XMPPConnection connection, OmemoDevice userDevice, BareJid contact)
747            throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
748            SmackException.NotConnectedException, SmackException.NoResponseException, IOException {
749        // refreshOmemoDeviceList;
750        OmemoDeviceListElement publishedList;
751        try {
752            publishedList = fetchDeviceList(connection, contact);
753        } catch (PubSubException.NotAPubSubNodeException e) {
754            LOGGER.log(Level.WARNING, "Error refreshing deviceList: ", e);
755            publishedList = null;
756        }
757        if (publishedList == null) {
758            publishedList = new OmemoDeviceListElement_VAxolotl(Collections.<Integer>emptySet());
759        }
760
761        return getOmemoStoreBackend().mergeCachedDeviceList(
762                userDevice, contact, publishedList);
763    }
764
765    /**
766     * Fetch the bundle of a contact and build a fresh OMEMO session with the contacts device.
767     * Note that this builds a fresh session, regardless if we have had a session before or not.
768     *
769     * @param connection authenticated XMPP connection
770     * @param userDevice our OmemoDevice
771     * @param contactsDevice OmemoDevice of a contact.
772     *
773     * @throws CannotEstablishOmemoSessionException if we cannot establish a session (because of missing bundle etc.)
774     * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
775     * @throws InterruptedException if the calling thread was interrupted.
776     * @throws SmackException.NoResponseException if there was no response from the remote entity.
777     * @throws CorruptedOmemoKeyException if our IdentityKeyPair is corrupted.
778     */
779    void buildFreshSessionWithDevice(XMPPConnection connection, OmemoDevice userDevice, OmemoDevice contactsDevice)
780            throws CannotEstablishOmemoSessionException, SmackException.NotConnectedException, InterruptedException,
781            SmackException.NoResponseException, CorruptedOmemoKeyException {
782
783        if (contactsDevice.equals(userDevice)) {
784            // Do not build a session with yourself.
785            return;
786        }
787
788        OmemoBundleElement bundleElement;
789        try {
790            bundleElement = fetchBundle(connection, contactsDevice);
791        } catch (XMPPException.XMPPErrorException | PubSubException.NotALeafNodeException |
792                PubSubException.NotAPubSubNodeException e) {
793            throw new CannotEstablishOmemoSessionException(contactsDevice, e);
794        }
795
796        // Select random Bundle
797        HashMap<Integer, T_Bundle> bundlesList = getOmemoStoreBackend().keyUtil().BUNDLE.bundles(bundleElement, contactsDevice);
798        int randomIndex = new Random().nextInt(bundlesList.size());
799        T_Bundle randomPreKeyBundle = new ArrayList<>(bundlesList.values()).get(randomIndex);
800
801        // build the session
802        OmemoManager omemoManager = OmemoManager.getInstanceFor(connection, userDevice.getDeviceId());
803        processBundle(omemoManager, randomPreKeyBundle, contactsDevice);
804    }
805
806    /**
807     * Build sessions with all devices from the set, we don't have a session with yet.
808     * Return the set of all devices we have a session with afterwards.
809     *
810     * @param connection authenticated XMPP connection
811     * @param userDevice our OmemoDevice
812     * @param devices set of devices we may want to build a session with if necessary
813     * @return set of all devices with sessions
814     *
815     * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
816     * @throws InterruptedException if the calling thread was interrupted.
817     * @throws SmackException.NoResponseException if there was no response from the remote entity.
818     * @throws IOException if an I/O error occurred.
819     */
820    private Set<OmemoDevice> buildMissingSessionsWithDevices(XMPPConnection connection,
821                                                             OmemoDevice userDevice,
822                                                             Set<OmemoDevice> devices)
823            throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, IOException {
824
825        Set<OmemoDevice> devicesWithSession = new HashSet<>();
826        for (OmemoDevice device : devices) {
827
828            if (hasSession(userDevice, device)) {
829                devicesWithSession.add(device);
830                continue;
831            }
832
833            try {
834                buildFreshSessionWithDevice(connection, userDevice, device);
835                devicesWithSession.add(device);
836            } catch (CannotEstablishOmemoSessionException e) {
837                LOGGER.log(Level.WARNING, userDevice + " cannot establish session with " + device +
838                        " because their bundle could not be fetched.", e);
839            } catch (CorruptedOmemoKeyException e) {
840                LOGGER.log(Level.WARNING, userDevice + " could not establish session with " + device +
841                        "because their bundle seems to be corrupt.", e);
842            }
843
844        }
845
846        return devicesWithSession;
847    }
848
849    /**
850     * Return a set of all devices from the provided set, which trust level is undecided.
851     * A device is also considered undecided, if its fingerprint cannot be loaded.
852     *
853     * @param userDevice our OmemoDevice
854     * @param callback OmemoTrustCallback to query the trust decisions from
855     * @param devices set of OmemoDevices
856     * @return set of OmemoDevices which contains all devices from the set devices, which are undecided
857     *
858     * @throws IOException if an I/O error occurred.
859     */
860    private Set<OmemoDevice> getUndecidedDevices(OmemoDevice userDevice, OmemoTrustCallback callback, Set<OmemoDevice> devices) throws IOException {
861        Set<OmemoDevice> undecidedDevices = new HashSet<>();
862
863        for (OmemoDevice device : devices) {
864
865            OmemoFingerprint fingerprint;
866            try {
867                fingerprint = getOmemoStoreBackend().getFingerprint(userDevice, device);
868            } catch (CorruptedOmemoKeyException | NoIdentityKeyException e) {
869                LOGGER.log(Level.WARNING, "Could not load fingerprint of " + device, e);
870                undecidedDevices.add(device);
871                continue;
872            }
873
874            if (callback.getTrust(device, fingerprint) == TrustState.undecided) {
875                undecidedDevices.add(device);
876            }
877        }
878
879        return undecidedDevices;
880    }
881
882    /**
883     * Return true, if the OmemoManager of userDevice has a session with the contactsDevice.
884     *
885     * @param userDevice our OmemoDevice.
886     * @param contactsDevice OmemoDevice of the contact.
887     * @return true if userDevice has session with contactsDevice.
888     *
889     * @throws IOException if an I/O error occurred.
890     */
891    private boolean hasSession(OmemoDevice userDevice, OmemoDevice contactsDevice) throws IOException {
892        return getOmemoStoreBackend().loadRawSession(userDevice, contactsDevice) != null;
893    }
894
895    /**
896     * Process a received bundle. Typically that includes saving keys and building a session.
897     *
898     * @param omemoManager our OmemoManager
899     * @param contactsBundle bundle of the contact
900     * @param contactsDevice OmemoDevice of the contact
901     *
902     * @throws CorruptedOmemoKeyException if the OMEMO key is corrupted.
903     */
904    protected abstract void processBundle(OmemoManager omemoManager,
905                                          T_Bundle contactsBundle,
906                                          OmemoDevice contactsDevice)
907            throws CorruptedOmemoKeyException;
908
909    /**
910     * Returns true, if a rotation of the signed preKey is necessary.
911     *
912     * @param userDevice our OmemoDevice
913     * @return true if rotation is necessary
914     *
915     * @throws IOException if an I/O error occurred.
916     */
917    private boolean shouldRotateSignedPreKey(OmemoDevice userDevice) throws IOException {
918        if (!OmemoConfiguration.getRenewOldSignedPreKeys()) {
919            return false;
920        }
921
922        Date now = new Date();
923        Date lastRenewal = getOmemoStoreBackend().getDateOfLastSignedPreKeyRenewal(userDevice);
924
925        if (lastRenewal == null) {
926            lastRenewal = new Date();
927            getOmemoStoreBackend().setDateOfLastSignedPreKeyRenewal(userDevice, lastRenewal);
928        }
929
930        long allowedAgeMillis = MILLIS_PER_HOUR * OmemoConfiguration.getRenewOldSignedPreKeysAfterHours();
931        return now.getTime() - lastRenewal.getTime() > allowedAgeMillis;
932    }
933
934    /**
935     * Return a copy of our deviceList, but with stale devices marked as inactive.
936     * Never mark our own device as stale.
937     * This method ignores {@link OmemoConfiguration#getDeleteStaleDevices()}!
938     *
939     * In this case, a stale device is one of our devices, from which we haven't received an OMEMO message from
940     * for more than {@link OmemoConfiguration#getDeleteStaleDevicesAfterHours()} hours.
941     *
942     * @param userDevice our OmemoDevice
943     * @return our altered deviceList with stale devices marked as inactive.
944     *
945     * @throws IOException if an I/O error occurred.
946     */
947    private OmemoCachedDeviceList deleteStaleDevices(OmemoDevice userDevice) throws IOException {
948        OmemoCachedDeviceList deviceList = getOmemoStoreBackend().loadCachedDeviceList(userDevice);
949        int maxAgeHours = OmemoConfiguration.getDeleteStaleDevicesAfterHours();
950        return removeStaleDevicesFromDeviceList(userDevice, userDevice.getJid(), deviceList, maxAgeHours);
951    }
952
953    /**
954     * Return a copy of the given deviceList of user contact, but with stale devices marked as inactive.
955     * Never mark our own device as stale. If we haven't yet received a message from a device, store the current date
956     * as last date of message receipt to allow future decisions.
957     *
958     * A stale device is a device, from which we haven't received an OMEMO message from for more than
959     * "maxAgeMillis" milliseconds.
960     *
961     * @param userDevice our OmemoDevice.
962     * @param contact subjects BareJid.
963     * @param contactsDeviceList subjects deviceList.
964     * @return copy of subjects deviceList with stale devices marked as inactive.
965     *
966     * @throws IOException if an I/O error occurred.
967     */
968    private OmemoCachedDeviceList removeStaleDevicesFromDeviceList(OmemoDevice userDevice,
969                                                                   BareJid contact,
970                                                                   OmemoCachedDeviceList contactsDeviceList,
971                                                                   int maxAgeHours) throws IOException {
972        OmemoCachedDeviceList deviceList = new OmemoCachedDeviceList(contactsDeviceList); // Don't work on original list.
973
974        // Iterate through original list, but modify copy instead
975        for (int deviceId : contactsDeviceList.getActiveDevices()) {
976            OmemoDevice device = new OmemoDevice(contact, deviceId);
977
978            Date lastDeviceIdPublication = getOmemoStoreBackend().getDateOfLastDeviceIdPublication(userDevice, device);
979            if (lastDeviceIdPublication == null) {
980                lastDeviceIdPublication = new Date();
981                getOmemoStoreBackend().setDateOfLastDeviceIdPublication(userDevice, device, lastDeviceIdPublication);
982            }
983
984            Date lastMessageReceived = getOmemoStoreBackend().getDateOfLastReceivedMessage(userDevice, device);
985            if (lastMessageReceived == null) {
986                lastMessageReceived = new Date();
987                getOmemoStoreBackend().setDateOfLastReceivedMessage(userDevice, device, lastMessageReceived);
988            }
989
990            boolean stale = isStale(userDevice, device, lastDeviceIdPublication, maxAgeHours);
991            stale &= isStale(userDevice, device, lastMessageReceived, maxAgeHours);
992
993            if (stale) {
994                deviceList.addInactiveDevice(deviceId);
995            }
996        }
997        return deviceList;
998    }
999
1000
1001    /**
1002     * Remove our device from the collection of devices.
1003     *
1004     * @param userDevice our OmemoDevice
1005     * @param devices collection of OmemoDevices
1006     */
1007    static void removeOurDevice(OmemoDevice userDevice, Collection<OmemoDevice> devices) {
1008        if (devices.contains(userDevice)) {
1009            devices.remove(userDevice);
1010        }
1011    }
1012
1013    /**
1014     * Determine, whether another one of *our* devices is stale or not.
1015     *
1016     * @param userDevice our omemoDevice
1017     * @param subject another one of our devices
1018     * @param lastReceipt date of last received message from that device
1019     * @param maxAgeHours threshold
1020     *
1021     * @return true if the subject device is considered stale
1022     */
1023    static boolean isStale(OmemoDevice userDevice, OmemoDevice subject, Date lastReceipt, int maxAgeHours) {
1024        if (userDevice.equals(subject)) {
1025            return false;
1026        }
1027
1028        if (lastReceipt == null) {
1029            return false;
1030        }
1031
1032        long maxAgeMillis = MILLIS_PER_HOUR * maxAgeHours;
1033        Date now = new Date();
1034
1035        return now.getTime() - lastReceipt.getTime() > maxAgeMillis;
1036    }
1037
1038    /**
1039     * Gullible TrustCallback, which returns all queried identities as trusted.
1040     * This is only used for insensitive OMEMO messages like RatchetUpdateMessages.
1041     * DO NOT USE THIS FOR ANYTHING ELSE!
1042     */
1043    private static final OmemoTrustCallback gullibleTrustCallback = new OmemoTrustCallback() {
1044        @Override
1045        public TrustState getTrust(OmemoDevice device, OmemoFingerprint fingerprint) {
1046            return TrustState.trusted;
1047        }
1048
1049        @Override
1050        public void setTrust(OmemoDevice device, OmemoFingerprint fingerprint, TrustState state) {
1051            // Not needed
1052        }
1053    };
1054
1055    /**
1056     * Decrypt a possible OMEMO encrypted messages in a {@link MamManager.MamQuery}.
1057     * The returned list contains wrappers that either hold an {@link OmemoMessage} in case the message was decrypted
1058     * properly, otherwise it contains the message itself.
1059     *
1060     * @param managerGuard authenticated OmemoManager.
1061     * @param mamQuery Mam archive query
1062     * @return list of {@link MessageOrOmemoMessage MessageOrOmemoMessages}.
1063     *
1064     * @throws IOException if an I/O error occurred.
1065     */
1066    List<MessageOrOmemoMessage> decryptMamQueryResult(OmemoManager.LoggedInOmemoManager managerGuard,
1067                                                      MamManager.MamQuery mamQuery) throws IOException {
1068        List<MessageOrOmemoMessage> result = new ArrayList<>();
1069        for (Message message : mamQuery.getMessages()) {
1070            if (OmemoManager.stanzaContainsOmemoElement(message)) {
1071                OmemoElement element =
1072                        (OmemoElement) message.getExtensionElement(OmemoElement.NAME_ENCRYPTED, OmemoConstants.OMEMO_NAMESPACE_V_AXOLOTL);
1073                // Decrypt OMEMO messages
1074                try {
1075                    OmemoMessage.Received omemoMessage = decryptMessage(managerGuard, message.getFrom().asBareJid(), element);
1076                    result.add(new MessageOrOmemoMessage(omemoMessage));
1077                } catch (NoRawSessionException | CorruptedOmemoKeyException | CryptoFailedException e) {
1078                    LOGGER.log(Level.WARNING, "decryptMamQueryResult failed to decrypt message from "
1079                            + message.getFrom() + " due to corrupted session/key: " + e.getMessage());
1080                    result.add(new MessageOrOmemoMessage(message));
1081                }
1082            } else {
1083                // Wrap cleartext messages
1084                result.add(new MessageOrOmemoMessage(message));
1085            }
1086        }
1087
1088        return result;
1089    }
1090
1091
1092    @Override
1093    public void onOmemoCarbonCopyReceived(CarbonExtension.Direction direction,
1094                                          Message carbonCopy,
1095                                          Message wrappingMessage,
1096                                          OmemoManager.LoggedInOmemoManager managerGuard) throws IOException {
1097        OmemoManager manager = managerGuard.get();
1098        // Avoid the ratchet being manipulated and the bundle being published multiple times simultaneously
1099        synchronized (manager) {
1100            OmemoDevice userDevice = manager.getOwnDevice();
1101            OmemoElement element = (OmemoElement) carbonCopy.getExtensionElement(OmemoElement.NAME_ENCRYPTED, OmemoElement_VAxolotl.NAMESPACE);
1102            if (element == null) {
1103                return;
1104            }
1105
1106            OmemoMessage.Received decrypted;
1107            BareJid sender = carbonCopy.getFrom().asBareJid();
1108
1109            try {
1110                decrypted = decryptMessage(managerGuard, sender, element);
1111                manager.notifyOmemoCarbonCopyReceived(direction, carbonCopy, wrappingMessage, decrypted);
1112
1113                if (decrypted.isPreKeyMessage() && OmemoConfiguration.getCompleteSessionWithEmptyMessage()) {
1114                    LOGGER.log(Level.FINE, "Received a preKeyMessage in a carbon copy from " + decrypted.getSenderDevice() + ".\n" +
1115                            "Complete the session by sending an empty response message.");
1116                    try {
1117                        sendRatchetUpdate(managerGuard, decrypted.getSenderDevice());
1118                    } catch (CannotEstablishOmemoSessionException e) {
1119                        throw new AssertionError("Since we successfully received a message, we MUST be able to " +
1120                                "establish a session. " + e);
1121                    } catch (NoSuchAlgorithmException | InterruptedException | SmackException.NotConnectedException | SmackException.NoResponseException e) {
1122                        LOGGER.log(Level.WARNING, "Cannot send a ratchet update message.", e);
1123                    }
1124                }
1125            } catch (NoRawSessionException e) {
1126                OmemoDevice device = e.getDeviceWithoutSession();
1127                LOGGER.log(Level.WARNING, "No raw session found for contact " + device + ". ", e);
1128
1129                if (OmemoConfiguration.getRepairBrokenSessionsWithPreKeyMessages()) {
1130                    repairBrokenSessionWithPreKeyMessage(managerGuard, device);
1131                }
1132            } catch (CorruptedOmemoKeyException | CryptoFailedException e) {
1133                LOGGER.log(Level.WARNING, "Could not decrypt incoming carbon copy: ", e);
1134            }
1135
1136            // Upload fresh bundle.
1137            if (getOmemoStoreBackend().loadOmemoPreKeys(userDevice).size() < OmemoConstants.PRE_KEY_COUNT_PER_BUNDLE) {
1138                LOGGER.log(Level.FINE, "We used up a preKey. Upload a fresh bundle.");
1139                try {
1140                    getOmemoStoreBackend().replenishKeys(userDevice);
1141                    OmemoBundleElement bundleElement = getOmemoStoreBackend().packOmemoBundle(userDevice);
1142                    publishBundle(manager.getConnection(), userDevice, bundleElement);
1143                } catch (CorruptedOmemoKeyException | InterruptedException | SmackException.NoResponseException
1144                        | SmackException.NotConnectedException | XMPPException.XMPPErrorException
1145                        | NotALeafNodeException e) {
1146                    LOGGER.log(Level.WARNING, "Could not republish replenished bundle.", e);
1147                }
1148            }
1149        }
1150    }
1151
1152    @Override
1153    public void onOmemoMessageStanzaReceived(Stanza stanza, OmemoManager.LoggedInOmemoManager managerGuard) throws IOException {
1154        OmemoManager manager = managerGuard.get();
1155        // Avoid the ratchet being manipulated and the bundle being published multiple times simultaneously
1156        synchronized (manager) {
1157            OmemoDevice userDevice = manager.getOwnDevice();
1158            OmemoElement element = (OmemoElement) stanza.getExtensionElement(OmemoElement.NAME_ENCRYPTED, OmemoElement_VAxolotl.NAMESPACE);
1159            if (element == null) {
1160                return;
1161            }
1162
1163            OmemoMessage.Received decrypted;
1164            BareJid sender;
1165
1166            try {
1167                MultiUserChat muc = getMuc(manager.getConnection(), stanza.getFrom());
1168                if (muc != null) {
1169                    Occupant occupant = muc.getOccupant(stanza.getFrom().asEntityFullJidIfPossible());
1170                    if (occupant == null) {
1171                        LOGGER.log(Level.WARNING, "Cannot decrypt OMEMO MUC message; MUC Occupant is null.");
1172                        return;
1173                    }
1174                    Jid occupantJid = occupant.getJid();
1175
1176                    if (occupantJid == null) {
1177                        LOGGER.log(Level.WARNING, "Cannot decrypt OMEMO MUC message; Senders Jid is null. " +
1178                                stanza.getFrom());
1179                        return;
1180                    }
1181
1182                    sender = occupantJid.asBareJid();
1183
1184                    // try is for this
1185                    decrypted = decryptMessage(managerGuard, sender, element);
1186                    manager.notifyOmemoMucMessageReceived(muc, stanza, decrypted);
1187
1188                } else {
1189                    sender = stanza.getFrom().asBareJid();
1190
1191                    // and this
1192                    decrypted = decryptMessage(managerGuard, sender, element);
1193                    manager.notifyOmemoMessageReceived(stanza, decrypted);
1194                }
1195
1196                if (decrypted.isPreKeyMessage() && OmemoConfiguration.getCompleteSessionWithEmptyMessage()) {
1197                    LOGGER.log(Level.FINE, "Received a preKeyMessage from " + decrypted.getSenderDevice() + ".\n" +
1198                            "Complete the session by sending an empty response message.");
1199                    try {
1200                        sendRatchetUpdate(managerGuard, decrypted.getSenderDevice());
1201                    } catch (CannotEstablishOmemoSessionException e) {
1202                        throw new AssertionError("Since we successfully received a message, we MUST be able to " +
1203                                "establish a session. " + e);
1204                    } catch (NoSuchAlgorithmException | InterruptedException | SmackException.NotConnectedException | SmackException.NoResponseException e) {
1205                        LOGGER.log(Level.WARNING, "Cannot send a ratchet update message.", e);
1206                    }
1207                }
1208            } catch (NoRawSessionException e) {
1209                OmemoDevice device = e.getDeviceWithoutSession();
1210                LOGGER.log(Level.WARNING, "No raw session found for contact " + device + ". ", e);
1211
1212                if (OmemoConfiguration.getRepairBrokenSessionsWithPreKeyMessages()) {
1213                    repairBrokenSessionWithPreKeyMessage(managerGuard, device);
1214                }
1215            } catch (CorruptedOmemoKeyException | CryptoFailedException e) {
1216                LOGGER.log(Level.WARNING, "Could not decrypt incoming message: ", e);
1217            }
1218
1219            // Upload fresh bundle.
1220            if (getOmemoStoreBackend().loadOmemoPreKeys(userDevice).size() < OmemoConstants.PRE_KEY_COUNT_PER_BUNDLE) {
1221                LOGGER.log(Level.FINE, "We used up a preKey. Upload a fresh bundle.");
1222                try {
1223                    getOmemoStoreBackend().replenishKeys(userDevice);
1224                    OmemoBundleElement bundleElement = getOmemoStoreBackend().packOmemoBundle(userDevice);
1225                    publishBundle(manager.getConnection(), userDevice, bundleElement);
1226                } catch (CorruptedOmemoKeyException | InterruptedException | SmackException.NoResponseException
1227                        | SmackException.NotConnectedException | XMPPException.XMPPErrorException
1228                        | NotALeafNodeException e) {
1229                    LOGGER.log(Level.WARNING, "Could not republish replenished bundle.", e);
1230                }
1231            }
1232        }
1233    }
1234
1235    /**
1236     * Decrypt the OmemoElement inside the given Stanza and return it.
1237     * Return null if something goes wrong.
1238     *
1239     * @param stanza stanza
1240     * @param managerGuard authenticated OmemoManager
1241     * @return decrypted OmemoMessage or null
1242     *
1243     * @throws IOException if an I/O error occurred.
1244     */
1245    OmemoMessage.Received decryptStanza(Stanza stanza, OmemoManager.LoggedInOmemoManager managerGuard) throws IOException {
1246        OmemoManager manager = managerGuard.get();
1247        // Avoid the ratchet being manipulated and the bundle being published multiple times simultaneously
1248        synchronized (manager) {
1249            OmemoDevice userDevice = manager.getOwnDevice();
1250            OmemoElement element = (OmemoElement) stanza.getExtensionElement(OmemoElement.NAME_ENCRYPTED, OmemoElement_VAxolotl.NAMESPACE);
1251            if (element == null) {
1252                return null;
1253            }
1254
1255            OmemoMessage.Received decrypted = null;
1256            BareJid sender;
1257
1258            try {
1259                MultiUserChat muc = getMuc(manager.getConnection(), stanza.getFrom());
1260                if (muc != null) {
1261                    Occupant occupant = muc.getOccupant(stanza.getFrom().asEntityFullJidIfPossible());
1262                    Jid occupantJid = occupant.getJid();
1263
1264                    if (occupantJid == null) {
1265                        LOGGER.log(Level.WARNING, "MUC message received, but there is no way to retrieve the senders Jid. " +
1266                                stanza.getFrom());
1267                        return null;
1268                    }
1269
1270                    sender = occupantJid.asBareJid();
1271
1272                    // try is for this
1273                    decrypted = decryptMessage(managerGuard, sender, element);
1274
1275                } else {
1276                    sender = stanza.getFrom().asBareJid();
1277
1278                    // and this
1279                    decrypted = decryptMessage(managerGuard, sender, element);
1280                }
1281
1282                if (decrypted.isPreKeyMessage() && OmemoConfiguration.getCompleteSessionWithEmptyMessage()) {
1283                    LOGGER.log(Level.FINE, "Received a preKeyMessage from " + decrypted.getSenderDevice() + ".\n" +
1284                            "Complete the session by sending an empty response message.");
1285                    try {
1286                        sendRatchetUpdate(managerGuard, decrypted.getSenderDevice());
1287                    } catch (CannotEstablishOmemoSessionException e) {
1288                        throw new AssertionError("Since we successfully received a message, we MUST be able to " +
1289                                "establish a session. " + e);
1290                    } catch (NoSuchAlgorithmException | InterruptedException | SmackException.NotConnectedException | SmackException.NoResponseException e) {
1291                        LOGGER.log(Level.WARNING, "Cannot send a ratchet update message.", e);
1292                    }
1293                }
1294            } catch (NoRawSessionException e) {
1295                OmemoDevice device = e.getDeviceWithoutSession();
1296                LOGGER.log(Level.WARNING, "No raw session found for contact " + device + ". ", e);
1297
1298            } catch (CorruptedOmemoKeyException | CryptoFailedException e) {
1299                LOGGER.log(Level.WARNING, "Could not decrypt incoming message: ", e);
1300            }
1301
1302            // Upload fresh bundle.
1303            if (getOmemoStoreBackend().loadOmemoPreKeys(userDevice).size() < OmemoConstants.PRE_KEY_COUNT_PER_BUNDLE) {
1304                LOGGER.log(Level.FINE, "We used up a preKey. Upload a fresh bundle.");
1305                try {
1306                    getOmemoStoreBackend().replenishKeys(userDevice);
1307                    OmemoBundleElement bundleElement = getOmemoStoreBackend().packOmemoBundle(userDevice);
1308                    publishBundle(manager.getConnection(), userDevice, bundleElement);
1309                } catch (CorruptedOmemoKeyException | InterruptedException | SmackException.NoResponseException
1310                        | SmackException.NotConnectedException | XMPPException.XMPPErrorException
1311                        | NotALeafNodeException e) {
1312                    LOGGER.log(Level.WARNING, "Could not republish replenished bundle.", e);
1313                }
1314            }
1315            return decrypted;
1316        }
1317    }
1318
1319    /**
1320     * Fetch and process a fresh bundle and send an empty preKeyMessage in order to establish a fresh session.
1321     *
1322     * @param managerGuard authenticated OmemoManager.
1323     * @param brokenDevice device which session broke.
1324     *
1325     * @throws IOException if an I/O error occurred.
1326     */
1327    private void repairBrokenSessionWithPreKeyMessage(OmemoManager.LoggedInOmemoManager managerGuard,
1328                                                      OmemoDevice brokenDevice) throws IOException {
1329
1330        LOGGER.log(Level.WARNING, "Attempt to repair the session by sending a fresh preKey message to "
1331                + brokenDevice);
1332        OmemoManager manager = managerGuard.get();
1333        try {
1334            // Create fresh session and send new preKeyMessage.
1335            buildFreshSessionWithDevice(manager.getConnection(), manager.getOwnDevice(), brokenDevice);
1336            sendRatchetUpdate(managerGuard, brokenDevice);
1337
1338        } catch (CannotEstablishOmemoSessionException | CorruptedOmemoKeyException e) {
1339            LOGGER.log(Level.WARNING, "Unable to repair session with " + brokenDevice, e);
1340        } catch (SmackException.NotConnectedException | InterruptedException | SmackException.NoResponseException e) {
1341            LOGGER.log(Level.WARNING, "Could not fetch fresh bundle for " + brokenDevice, e);
1342        } catch (CryptoFailedException | NoSuchAlgorithmException e) {
1343            LOGGER.log(Level.WARNING, "Could not create PreKeyMessage", e);
1344        }
1345    }
1346
1347    /**
1348     * Send an empty OMEMO message to contactsDevice in order to forward the ratchet.
1349     *
1350     * @param managerGuard OMEMO manager
1351     * @param contactsDevice contacts OMEMO device
1352     *
1353     * @throws CorruptedOmemoKeyException if our or their OMEMO key is corrupted.
1354     * @throws InterruptedException if the calling thread was interrupted.
1355     * @throws SmackException.NoResponseException if there was no response from the remote entity.
1356     * @throws NoSuchAlgorithmException if AES encryption fails
1357     * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
1358     * @throws CryptoFailedException if encryption fails (should not happen though, but who knows...)
1359     * @throws CannotEstablishOmemoSessionException if we cannot establish a session with contactsDevice.
1360     * @throws IOException if an I/O error occurred.
1361     */
1362    private void sendRatchetUpdate(OmemoManager.LoggedInOmemoManager managerGuard, OmemoDevice contactsDevice)
1363            throws CorruptedOmemoKeyException, InterruptedException, SmackException.NoResponseException,
1364            NoSuchAlgorithmException, SmackException.NotConnectedException, CryptoFailedException,
1365            CannotEstablishOmemoSessionException, IOException {
1366
1367        OmemoManager manager = managerGuard.get();
1368        OmemoElement ratchetUpdate = createRatchetUpdateElement(managerGuard, contactsDevice);
1369
1370        XMPPConnection connection = manager.getConnection();
1371        Message message = connection.getStanzaFactory().buildMessageStanza()
1372                .to(contactsDevice.getJid())
1373                .addExtension(ratchetUpdate)
1374                .build();
1375        connection.sendStanza(message);
1376    }
1377
1378    /**
1379     * Return the joined MUC with EntityBareJid jid, or null if its not a room and/or not joined.
1380     *
1381     * @param connection xmpp connection
1382     * @param jid jid (presumably) of the MUC
1383     * @return MultiUserChat or null if not a MUC.
1384     */
1385    private static MultiUserChat getMuc(XMPPConnection connection, Jid jid) {
1386        EntityBareJid ebj = jid.asEntityBareJidIfPossible();
1387        if (ebj == null) {
1388            return null;
1389        }
1390
1391        MultiUserChatManager mucm = MultiUserChatManager.getInstanceFor(connection);
1392        Set<EntityBareJid> joinedRooms = mucm.getJoinedRooms();
1393        if (joinedRooms.contains(ebj)) {
1394            return mucm.getMultiUserChat(ebj);
1395        }
1396
1397        return null;
1398    }
1399
1400    /**
1401     * Publish a new DeviceList with just our device in it.
1402     *
1403     * @param managerGuard authenticated OmemoManager.
1404     *
1405     * @throws InterruptedException if the calling thread was interrupted.
1406     * @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
1407     * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
1408     * @throws SmackException.NoResponseException if there was no response from the remote entity.
1409     * @throws IOException if an I/O error occurred.
1410     * @throws NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
1411     */
1412    public void purgeDeviceList(OmemoManager.LoggedInOmemoManager managerGuard)
1413            throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException,
1414            SmackException.NoResponseException, IOException, NotALeafNodeException {
1415
1416        OmemoManager omemoManager = managerGuard.get();
1417        OmemoDevice userDevice = omemoManager.getOwnDevice();
1418
1419        OmemoDeviceListElement_VAxolotl newList =
1420                new OmemoDeviceListElement_VAxolotl(Collections.singleton(userDevice.getDeviceId()));
1421
1422        // Merge list
1423        getOmemoStoreBackend().mergeCachedDeviceList(userDevice, userDevice.getJid(), newList);
1424
1425        OmemoService.publishDeviceList(omemoManager.getConnection(), newList);
1426    }
1427}