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