001/**
002 *
003 * Copyright 2017 Paul Schaub
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.OMEMO_NAMESPACE_V_AXOLOTL;
020import static org.jivesoftware.smackx.omemo.util.OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID;
021import static org.jivesoftware.smackx.omemo.util.OmemoConstants.PEP_NODE_DEVICE_LIST;
022
023import java.io.UnsupportedEncodingException;
024import java.security.InvalidAlgorithmParameterException;
025import java.security.InvalidKeyException;
026import java.security.NoSuchAlgorithmException;
027import java.security.NoSuchProviderException;
028import java.security.Security;
029import java.util.ArrayList;
030import java.util.Collections;
031import java.util.Date;
032import java.util.HashMap;
033import java.util.HashSet;
034import java.util.Iterator;
035import java.util.List;
036import java.util.Map;
037import java.util.Random;
038import java.util.Set;
039import java.util.logging.Level;
040import java.util.logging.Logger;
041import javax.crypto.BadPaddingException;
042import javax.crypto.IllegalBlockSizeException;
043import javax.crypto.NoSuchPaddingException;
044
045import org.jivesoftware.smack.SmackException;
046import org.jivesoftware.smack.StanzaListener;
047import org.jivesoftware.smack.XMPPException;
048import org.jivesoftware.smack.filter.StanzaFilter;
049import org.jivesoftware.smack.packet.Message;
050import org.jivesoftware.smack.packet.Stanza;
051import org.jivesoftware.smack.packet.XMPPError;
052import org.jivesoftware.smack.util.Async;
053
054import org.jivesoftware.smackx.carbons.CarbonCopyReceivedListener;
055import org.jivesoftware.smackx.carbons.CarbonManager;
056import org.jivesoftware.smackx.carbons.packet.CarbonExtension;
057import org.jivesoftware.smackx.forward.packet.Forwarded;
058import org.jivesoftware.smackx.mam.MamManager;
059import org.jivesoftware.smackx.muc.MultiUserChat;
060import org.jivesoftware.smackx.muc.MultiUserChatManager;
061import org.jivesoftware.smackx.omemo.element.OmemoBundleVAxolotlElement;
062import org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement;
063import org.jivesoftware.smackx.omemo.element.OmemoDeviceListVAxolotlElement;
064import org.jivesoftware.smackx.omemo.element.OmemoElement;
065import org.jivesoftware.smackx.omemo.element.OmemoVAxolotlElement;
066import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException;
067import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
068import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
069import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException;
070import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException;
071import org.jivesoftware.smackx.omemo.internal.CachedDeviceList;
072import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
073import org.jivesoftware.smackx.omemo.internal.ClearTextMessage;
074import org.jivesoftware.smackx.omemo.internal.IdentityKeyWrapper;
075import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
076import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation;
077import org.jivesoftware.smackx.omemo.internal.OmemoSession;
078import org.jivesoftware.smackx.omemo.util.OmemoConstants;
079import org.jivesoftware.smackx.omemo.util.OmemoMessageBuilder;
080import org.jivesoftware.smackx.pubsub.LeafNode;
081import org.jivesoftware.smackx.pubsub.PayloadItem;
082import org.jivesoftware.smackx.pubsub.PubSubException;
083import org.jivesoftware.smackx.pubsub.PubSubException.NotAPubSubNodeException;
084import org.jivesoftware.smackx.pubsub.PubSubManager;
085
086import org.bouncycastle.jce.provider.BouncyCastleProvider;
087import org.jxmpp.jid.BareJid;
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 * @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
106    static {
107        Security.addProvider(new BouncyCastleProvider());
108    }
109
110    protected static final Logger LOGGER = Logger.getLogger(OmemoService.class.getName());
111
112    private static OmemoService<?, ?, ?, ?, ?, ?, ?, ?, ?> INSTANCE;
113
114    protected OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore;
115
116    public static OmemoService<?, ?, ?, ?, ?, ?, ?, ?, ?> getInstance() {
117        if (INSTANCE == null) {
118            throw new IllegalStateException("No OmemoService registered");
119        }
120        return INSTANCE;
121    }
122
123    /**
124     * Set singleton instance. Throws an IllegalStateException, if there is already a service set as instance.
125     *
126     * @param omemoService instance
127     */
128    protected static void setInstance(OmemoService<?, ?, ?, ?, ?, ?, ?, ?, ?> omemoService) {
129        if (INSTANCE != null) {
130            throw new IllegalStateException("An OmemoService is already registered");
131        }
132        INSTANCE = omemoService;
133    }
134
135    public static boolean isServiceRegistered() {
136        return INSTANCE != null;
137    }
138
139    /**
140     * Return the used omemoStore backend.
141     * If there is no store backend set yet, set the default one (typically a file-based one).
142     *
143     * @return omemoStore backend
144     */
145    public OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
146    getOmemoStoreBackend() {
147        if (omemoStore == null) {
148            setOmemoStoreBackend(createDefaultOmemoStoreBackend());
149            return getOmemoStoreBackend();
150        }
151        return omemoStore;
152    }
153
154    /**
155     * Set an omemoStore as backend. Throws an IllegalStateException, if there is already a backend set.
156     *
157     * @param omemoStore store.
158     */
159    public void setOmemoStoreBackend(
160            OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore) {
161        if (this.omemoStore != null) {
162            throw new IllegalStateException("An OmemoStore backend has already been set.");
163        }
164        this.omemoStore = omemoStore;
165    }
166
167    /**
168     * Create a default OmemoStore object.
169     *
170     * @return default omemoStore.
171     */
172    public abstract OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
173    createDefaultOmemoStoreBackend();
174
175    /**
176     * Create a new OmemoService object. This should only happen once.
177     * When the service gets created, it tries a placeholder crypto function in order to test, if all necessary
178     * algorithms are available on the system.
179     *
180     * @throws NoSuchPaddingException               When no Cipher could be instantiated.
181     * @throws NoSuchAlgorithmException             when no Cipher could be instantiated.
182     * @throws NoSuchProviderException              when BouncyCastle could not be found.
183     * @throws InvalidAlgorithmParameterException   when the Cipher could not be initialized
184     * @throws InvalidKeyException                  when the generated key is invalid
185     * @throws UnsupportedEncodingException         when UTF8 is unavailable
186     * @throws BadPaddingException                  when cipher.doFinal gets wrong padding
187     * @throws IllegalBlockSizeException            when cipher.doFinal gets wrong Block size.
188     */
189    public OmemoService()
190            throws NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException, IllegalBlockSizeException,
191            BadPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
192
193        // Check availability of algorithms and encodings needed for crypto
194        checkAvailableAlgorithms();
195    }
196
197    /**
198     * Initialize OMEMO functionality for OmemoManager omemoManager.
199     *
200     * @param omemoManager OmemoManager we'd like to initialize.
201     * @throws InterruptedException
202     * @throws CorruptedOmemoKeyException
203     * @throws XMPPException.XMPPErrorException
204     * @throws SmackException.NotConnectedException
205     * @throws SmackException.NoResponseException
206     * @throws SmackException.NotLoggedInException
207     * @throws PubSubException.NotALeafNodeException
208     */
209    void initialize(OmemoManager omemoManager) throws InterruptedException, CorruptedOmemoKeyException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException, SmackException.NotLoggedInException, PubSubException.NotALeafNodeException {
210        if (!omemoManager.getConnection().isAuthenticated()) {
211            throw new SmackException.NotLoggedInException();
212        }
213
214        boolean mustPublishId = false;
215        if (getOmemoStoreBackend().isFreshInstallation(omemoManager)) {
216            LOGGER.log(Level.INFO, "No key material found. Looks like we have a fresh installation.");
217            // Create new key material and publish it to the server
218            regenerate(omemoManager, omemoManager.getDeviceId());
219            mustPublishId = true;
220        }
221
222        // Get fresh device list from server
223        mustPublishId |= refreshOwnDeviceList(omemoManager);
224
225        publishDeviceIdIfNeeded(omemoManager, false, mustPublishId);
226        publishBundle(omemoManager);
227
228        registerOmemoMessageStanzaListeners(omemoManager);  //Wait for new OMEMO messages
229        getOmemoStoreBackend().initializeOmemoSessions(omemoManager);   //Preload existing OMEMO sessions
230    }
231
232    /**
233     * Test availability of required algorithms. We do this in advance, so we can simplify exception handling later.
234     *
235     * @throws NoSuchPaddingException
236     * @throws UnsupportedEncodingException
237     * @throws InvalidAlgorithmParameterException
238     * @throws NoSuchAlgorithmException
239     * @throws IllegalBlockSizeException
240     * @throws BadPaddingException
241     * @throws NoSuchProviderException
242     * @throws InvalidKeyException
243     */
244    protected static void checkAvailableAlgorithms() throws NoSuchPaddingException, UnsupportedEncodingException,
245            InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException,
246            NoSuchProviderException, InvalidKeyException {
247        // Test crypto functions
248        new OmemoMessageBuilder<>(null, null, "");
249    }
250
251    /**
252     * Generate a new unique deviceId and regenerate new keys.
253     *
254     * @param omemoManager  OmemoManager we want to regenerate.
255     * @param nDeviceId     new DeviceId we want to use with the newly generated keys.
256     * @throws CorruptedOmemoKeyException when freshly generated identityKey is invalid
257     *                                  (should never ever happen *crosses fingers*)
258     */
259    void regenerate(OmemoManager omemoManager, Integer nDeviceId) throws CorruptedOmemoKeyException {
260        // Generate unique ID that is not already taken
261        while (nDeviceId == null || !getOmemoStoreBackend().isAvailableDeviceId(omemoManager, nDeviceId)) {
262            nDeviceId = OmemoManager.randomDeviceId();
263        }
264
265        getOmemoStoreBackend().forgetOmemoSessions(omemoManager);
266        getOmemoStoreBackend().purgeOwnDeviceKeys(omemoManager);
267        omemoManager.setDeviceId(nDeviceId);
268        getOmemoStoreBackend().regenerate(omemoManager);
269    }
270
271    /**
272     * Publish a fresh bundle to the server.
273     *
274     * @param omemoManager OmemoManager
275     * @throws SmackException.NotConnectedException
276     * @throws InterruptedException
277     * @throws SmackException.NoResponseException
278     * @throws CorruptedOmemoKeyException
279     * @throws XMPPException.XMPPErrorException
280     */
281    void publishBundle(OmemoManager omemoManager)
282            throws SmackException.NotConnectedException, InterruptedException,
283            SmackException.NoResponseException, CorruptedOmemoKeyException, XMPPException.XMPPErrorException {
284        Date lastSignedPreKeyRenewal = getOmemoStoreBackend().getDateOfLastSignedPreKeyRenewal(omemoManager);
285        if (OmemoConfiguration.getRenewOldSignedPreKeys() && lastSignedPreKeyRenewal != null) {
286            if (System.currentTimeMillis() - lastSignedPreKeyRenewal.getTime()
287                    > 1000L * 60 * 60 * OmemoConfiguration.getRenewOldSignedPreKeysAfterHours()) {
288                LOGGER.log(Level.INFO, "Renewing signedPreKey");
289                getOmemoStoreBackend().changeSignedPreKey(omemoManager);
290            }
291        } else {
292            getOmemoStoreBackend().setDateOfLastSignedPreKeyRenewal(omemoManager);
293        }
294
295        // publish
296        PubSubManager.getInstance(omemoManager.getConnection(), omemoManager.getOwnJid())
297                .tryToPublishAndPossibleAutoCreate(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(omemoManager.getDeviceId()),
298                        new PayloadItem<>(getOmemoStoreBackend().packOmemoBundle(omemoManager)));
299    }
300
301    /**
302     * Publish our deviceId in case it is not on the list already.
303     * This method calls publishDeviceIdIfNeeded(omemoManager, deleteOtherDevices, false).
304     * @param omemoManager          OmemoManager
305     * @param deleteOtherDevices    Do we want to remove other devices from the list?
306     * @throws InterruptedException
307     * @throws PubSubException.NotALeafNodeException
308     * @throws XMPPException.XMPPErrorException
309     * @throws SmackException.NotConnectedException
310     * @throws SmackException.NoResponseException
311     */
312    void publishDeviceIdIfNeeded(OmemoManager omemoManager, boolean deleteOtherDevices) throws InterruptedException,
313            PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
314            SmackException.NotConnectedException, SmackException.NoResponseException {
315        publishDeviceIdIfNeeded(omemoManager, deleteOtherDevices, false);
316    }
317
318    /**
319     * Publish our deviceId in case it is not on the list already.
320     *
321     * @param omemoManager       OmemoManager
322     * @param deleteOtherDevices Do we want to remove other devices from the list?
323     *                           If we do, publish the list with only our id, regardless if we were on the list
324     *                           already.
325     * @param publish            Do we want to force publishing our id?
326     * @throws SmackException.NotConnectedException
327     * @throws InterruptedException
328     * @throws SmackException.NoResponseException
329     * @throws XMPPException.XMPPErrorException
330     * @throws PubSubException.NotALeafNodeException
331     */
332    void publishDeviceIdIfNeeded(OmemoManager omemoManager, boolean deleteOtherDevices, boolean publish)
333            throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException,
334            XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException {
335
336        CachedDeviceList deviceList = getOmemoStoreBackend().loadCachedDeviceList(omemoManager, omemoManager.getOwnJid());
337
338        Set<Integer> deviceListIds;
339        if (deviceList == null) {
340            deviceListIds = new HashSet<>();
341        } else {
342            deviceListIds = new HashSet<>(deviceList.getActiveDevices());
343        }
344
345        if (deleteOtherDevices) {
346            deviceListIds.clear();
347        }
348
349        int ourDeviceId = omemoManager.getDeviceId();
350        if (deviceListIds.add(ourDeviceId)) {
351            publish = true;
352        }
353
354        publish |= removeStaleDevicesIfNeeded(omemoManager, deviceListIds);
355
356        if (publish) {
357            publishDeviceIds(omemoManager, new OmemoDeviceListVAxolotlElement(deviceListIds));
358        }
359    }
360
361    /**
362     * Remove stale devices from our device list.
363     * This does only delete devices, if that's configured in OmemoConfiguration.
364     *
365     * @param omemoManager  OmemoManager
366     * @param deviceListIds deviceIds we plan to publish. Stale devices are deleted from that list.
367     * @return
368     */
369    boolean removeStaleDevicesIfNeeded(OmemoManager omemoManager, Set<Integer> deviceListIds) {
370        boolean publish = false;
371        int ownDeviceId = omemoManager.getDeviceId();
372        // Clear devices that we didn't receive a message from for a while
373        Iterator<Integer> it = deviceListIds.iterator();
374        while (OmemoConfiguration.getDeleteStaleDevices() && it.hasNext()) {
375            int id = it.next();
376            if (id == ownDeviceId) {
377                // Skip own id
378                continue;
379            }
380
381            OmemoDevice d = new OmemoDevice(omemoManager.getOwnJid(), id);
382            Date date = getOmemoStoreBackend().getDateOfLastReceivedMessage(omemoManager, d);
383
384            if (date == null) {
385                getOmemoStoreBackend().setDateOfLastReceivedMessage(omemoManager, d);
386            } else {
387                if (System.currentTimeMillis() - date.getTime() > 1000L * 60 * 60 * OmemoConfiguration.getDeleteStaleDevicesAfterHours()) {
388                    LOGGER.log(Level.INFO, "Remove device " + id + " because of more than " +
389                            OmemoConfiguration.getDeleteStaleDevicesAfterHours() + " hours of inactivity.");
390                    it.remove();
391                    publish = true;
392                }
393            }
394        }
395        return publish;
396    }
397
398    /**
399     * Publish the given deviceList to the server.
400     *
401     * @param omemoManager OmemoManager
402     * @param deviceList list of deviceIDs
403     * @throws InterruptedException                 Exception
404     * @throws XMPPException.XMPPErrorException     Exception
405     * @throws SmackException.NotConnectedException Exception
406     * @throws SmackException.NoResponseException   Exception
407     * @throws PubSubException.NotALeafNodeException Exception
408     */
409    static void publishDeviceIds(OmemoManager omemoManager, OmemoDeviceListElement deviceList)
410            throws InterruptedException, XMPPException.XMPPErrorException,
411            SmackException.NotConnectedException, SmackException.NoResponseException, PubSubException.NotALeafNodeException {
412        PubSubManager.getInstance(omemoManager.getConnection(), omemoManager.getOwnJid())
413                .tryToPublishAndPossibleAutoCreate(OmemoConstants.PEP_NODE_DEVICE_LIST, new PayloadItem<>(deviceList));
414    }
415
416    /**
417     * Fetch the deviceList node of a contact.
418     *
419     * @param omemoManager omemoManager
420     * @param contact contact
421     * @return LeafNode
422     * @throws InterruptedException
423     * @throws PubSubException.NotALeafNodeException
424     * @throws XMPPException.XMPPErrorException
425     * @throws SmackException.NotConnectedException
426     * @throws SmackException.NoResponseException
427     * @throws NotAPubSubNodeException 
428     */
429    static LeafNode fetchDeviceListNode(OmemoManager omemoManager, BareJid contact)
430            throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
431            SmackException.NotConnectedException, SmackException.NoResponseException, NotAPubSubNodeException {
432        return PubSubManager.getInstance(omemoManager.getConnection(), contact).getLeafNode(PEP_NODE_DEVICE_LIST);
433    }
434
435    /**
436     * Directly fetch the device list of a contact.
437     *
438     * @param omemoManager OmemoManager
439     * @param contact BareJid of the contact
440     * @return The OmemoDeviceListElement of the contact
441     * @throws XMPPException.XMPPErrorException     When
442     * @throws SmackException.NotConnectedException something
443     * @throws InterruptedException                 goes
444     * @throws SmackException.NoResponseException   wrong
445     * @throws PubSubException.NotALeafNodeException when the device lists node is not a LeafNode
446     * @throws NotAPubSubNodeException 
447     */
448    static OmemoDeviceListElement fetchDeviceList(OmemoManager omemoManager, BareJid contact)
449                    throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
450                    SmackException.NoResponseException, PubSubException.NotALeafNodeException, NotAPubSubNodeException {
451        return extractDeviceListFrom(fetchDeviceListNode(omemoManager, contact));
452    }
453
454    /**
455     * Refresh our deviceList from the server.
456     *
457     * @param omemoManager omemoManager
458     * @return true, if we should publish our device list again (because its broken or not existent...)
459     *
460     * @throws SmackException.NotConnectedException
461     * @throws InterruptedException
462     * @throws SmackException.NoResponseException
463     */
464    private boolean refreshOwnDeviceList(OmemoManager omemoManager) throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, XMPPException.XMPPErrorException {
465        try {
466            getOmemoStoreBackend().mergeCachedDeviceList(omemoManager, omemoManager.getOwnJid(),
467                    fetchDeviceList(omemoManager, omemoManager.getOwnJid()));
468
469        } catch (XMPPException.XMPPErrorException e) {
470
471            if (e.getXMPPError().getCondition() == XMPPError.Condition.item_not_found) {
472                LOGGER.log(Level.WARNING, "Could not refresh own deviceList, because the node did not exist: "
473                        + e.getMessage());
474                return true;
475            }
476
477            throw e;
478
479        } catch (PubSubException.NotALeafNodeException e) {
480            LOGGER.log(Level.WARNING, "Could not refresh own deviceList, because the Node is not a LeafNode: " +
481                    e.getMessage());
482        }
483
484        catch (PubSubException.NotAPubSubNodeException e) {
485            LOGGER.log(Level.WARNING, "Caught a PubSubAssertionError when fetching a deviceList node. " +
486                    "This probably means that we're dealing with an ejabberd server and the LeafNode does not exist.", e);
487            return true;
488        }
489        return false;
490    }
491
492    /**
493     * Refresh the deviceList of contact and merge it with the one stored locally.
494     * @param omemoManager omemoManager
495     * @param contact contact
496     * @throws SmackException.NotConnectedException
497     * @throws InterruptedException
498     * @throws SmackException.NoResponseException
499     */
500    void refreshDeviceList(OmemoManager omemoManager, BareJid contact) throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
501        OmemoDeviceListElement omemoDeviceListElement;
502        try {
503            omemoDeviceListElement = fetchDeviceList(omemoManager, contact);
504        } catch (PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException e) {
505            LOGGER.log(Level.WARNING, "Could not fetch device list of " + contact + ": " + e, e);
506            return;
507        }
508        catch (NotAPubSubNodeException e) {
509            LOGGER.log(Level.WARNING, "Could not fetch device list of " + contact ,e);
510            return;
511        }
512
513        getOmemoStoreBackend().mergeCachedDeviceList(omemoManager, contact, omemoDeviceListElement);
514    }
515
516    /**
517     * Fetch the OmemoBundleElement of the contact.
518     *
519     * @param omemoManager OmemoManager
520     * @param contact the contacts BareJid
521     * @return the OmemoBundleElement of the contact
522     * @throws XMPPException.XMPPErrorException     When
523     * @throws SmackException.NotConnectedException something
524     * @throws InterruptedException                 goes
525     * @throws SmackException.NoResponseException   wrong
526     * @throws PubSubException.NotALeafNodeException when the bundles node is not a LeafNode
527     * @throws NotAPubSubNodeException 
528     */
529    static OmemoBundleVAxolotlElement fetchBundle(OmemoManager omemoManager, OmemoDevice contact)
530                    throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
531                    SmackException.NoResponseException, PubSubException.NotALeafNodeException, NotAPubSubNodeException {
532        LeafNode node = PubSubManager.getInstance(omemoManager.getConnection(), contact.getJid()).getLeafNode(
533                        PEP_NODE_BUNDLE_FROM_DEVICE_ID(contact.getDeviceId()));
534        return extractBundleFrom(node);
535    }
536
537    /**
538     * Extract the OmemoBundleElement of a contact from a LeafNode.
539     *
540     * @param node typically a LeafNode containing the OmemoBundles of a contact
541     * @return the OmemoBundleElement
542     * @throws XMPPException.XMPPErrorException     When
543     * @throws SmackException.NotConnectedException something
544     * @throws InterruptedException                 goes
545     * @throws SmackException.NoResponseException   wrong
546     */
547    private static OmemoBundleVAxolotlElement extractBundleFrom(LeafNode node) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
548        if (node == null) {
549            return null;
550        }
551        try {
552            return (OmemoBundleVAxolotlElement) ((PayloadItem<?>) node.getItems().get(0)).getPayload();
553        } catch (IndexOutOfBoundsException e) {
554            return null;
555        }
556    }
557
558    /**
559     * Extract the OmemoDeviceListElement of a contact from a node containing his OmemoDeviceListElement.
560     *
561     * @param node typically a LeafNode containing the OmemoDeviceListElement of a contact
562     * @return the extracted OmemoDeviceListElement.
563     * @throws XMPPException.XMPPErrorException     When
564     * @throws SmackException.NotConnectedException something
565     * @throws InterruptedException                 goes
566     * @throws SmackException.NoResponseException   wrong
567     */
568    private static OmemoDeviceListElement extractDeviceListFrom(LeafNode node) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
569        if (node == null) {
570            LOGGER.log(Level.WARNING, "DeviceListNode is null.");
571            return null;
572        }
573        List<?> items = node.getItems();
574        if (items.size() > 0) {
575            OmemoDeviceListVAxolotlElement listElement = (OmemoDeviceListVAxolotlElement) ((PayloadItem<?>) items.get(items.size() - 1)).getPayload();
576            if (items.size() > 1) {
577                node.deleteAllItems();
578                node.publish(new PayloadItem<>(listElement));
579            }
580            return listElement;
581        }
582
583        Set<Integer> emptySet = Collections.emptySet();
584        return new OmemoDeviceListVAxolotlElement(emptySet);
585    }
586
587    /**
588     * Build sessions for all devices of the contact that we do not have a session with yet.
589     *
590     * @param omemoManager omemoManager
591     * @param jid the BareJid of the contact
592     */
593    void buildOrCreateOmemoSessionsFromBundles(OmemoManager omemoManager, BareJid jid) throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, CannotEstablishOmemoSessionException {
594        CachedDeviceList devices = getOmemoStoreBackend().loadCachedDeviceList(omemoManager, jid);
595        CannotEstablishOmemoSessionException sessionException = null;
596        if (devices == null || devices.getAllDevices().isEmpty()) {
597            refreshDeviceList(omemoManager, jid);
598            devices = getOmemoStoreBackend().loadCachedDeviceList(omemoManager, jid);
599        }
600
601        for (int id : devices.getActiveDevices()) {
602            OmemoDevice device = new OmemoDevice(jid, id);
603            if (getOmemoStoreBackend().containsRawSession(omemoManager, device)) {
604                // We have a session already.
605                continue;
606            }
607
608            // Build missing session
609            try {
610                buildSessionFromOmemoBundle(omemoManager, device, false);
611            } catch (CannotEstablishOmemoSessionException e) {
612
613                if (sessionException == null) {
614                    sessionException = e;
615                } else {
616                    sessionException.addFailures(e);
617                }
618
619            } catch (CorruptedOmemoKeyException e) {
620                CannotEstablishOmemoSessionException fail =
621                        new CannotEstablishOmemoSessionException(device, e);
622
623                if (sessionException == null) {
624                    sessionException = fail;
625                } else {
626                    sessionException.addFailures(fail);
627                }
628            }
629        }
630
631        if (sessionException != null) {
632            throw sessionException;
633        }
634    }
635
636    /**
637     * Build an OmemoSession for the given OmemoDevice.
638     *
639     * @param omemoManager omemoManager
640     * @param device OmemoDevice
641     * @param fresh Do we want to build a session even if we already have one?
642     * @throws CannotEstablishOmemoSessionException when no session could be established
643     * @throws CorruptedOmemoKeyException when the bundle contained an invalid OMEMO identityKey
644     */
645    public void buildSessionFromOmemoBundle(OmemoManager omemoManager, OmemoDevice device, boolean fresh) throws CannotEstablishOmemoSessionException, CorruptedOmemoKeyException {
646
647        if (device.equals(omemoManager.getOwnDevice())) {
648            return;
649        }
650
651        // Do not build sessions with devices we already know...
652        if (!fresh && getOmemoStoreBackend().containsRawSession(omemoManager, device)) {
653            getOmemoStoreBackend().getOmemoSessionOf(omemoManager, device); //Make sure its loaded though
654            return;
655        }
656
657        OmemoBundleVAxolotlElement bundle;
658        try {
659            bundle = fetchBundle(omemoManager, device);
660        } catch (SmackException | XMPPException.XMPPErrorException | InterruptedException e) {
661            throw new CannotEstablishOmemoSessionException(device, e);
662        }
663
664        HashMap<Integer, T_Bundle> bundles = getOmemoStoreBackend().keyUtil().BUNDLE.bundles(bundle, device);
665
666        // Select random Bundle
667        int randomIndex = new Random().nextInt(bundles.size());
668        T_Bundle randomPreKeyBundle = new ArrayList<>(bundles.values()).get(randomIndex);
669        // Build raw session
670        processBundle(omemoManager, randomPreKeyBundle, device);
671    }
672
673    /**
674     * Process a received bundle. Typically that includes saving keys and building a session.
675     *
676     * @param omemoManager omemoManager that will process the bundle
677     * @param bundle T_Bundle (depends on used Signal/Olm library)
678     * @param device OmemoDevice
679     * @throws CorruptedOmemoKeyException
680     */
681    protected abstract void processBundle(OmemoManager omemoManager, T_Bundle bundle, OmemoDevice device) throws CorruptedOmemoKeyException;
682
683    /**
684     * Process a received message. Try to decrypt it in case we are a recipient device. If we are not a recipient
685     * device, return null.
686     *
687     * @param sender        the BareJid of the sender of the message
688     * @param message       the encrypted message
689     * @param information   OmemoMessageInformation object which will contain meta data about the decrypted message
690     * @return decrypted message or null
691     * @throws NoRawSessionException
692     * @throws InterruptedException
693     * @throws SmackException.NoResponseException
694     * @throws SmackException.NotConnectedException
695     * @throws CryptoFailedException
696     * @throws XMPPException.XMPPErrorException
697     * @throws CorruptedOmemoKeyException
698     */
699    private Message processReceivingMessage(OmemoManager omemoManager, OmemoDevice sender, OmemoElement message, final OmemoMessageInformation information)
700            throws NoRawSessionException, InterruptedException, SmackException.NoResponseException, SmackException.NotConnectedException,
701            CryptoFailedException, XMPPException.XMPPErrorException, CorruptedOmemoKeyException {
702
703        ArrayList<OmemoVAxolotlElement.OmemoHeader.Key> messageRecipientKeys = message.getHeader().getKeys();
704        // Do we have a key with our ID in the message?
705        for (OmemoVAxolotlElement.OmemoHeader.Key k : messageRecipientKeys) {
706            // Only decrypt with our deviceID
707            if (k.getId() != omemoManager.getDeviceId()) {
708                continue;
709            }
710
711            Message decrypted = decryptOmemoMessageElement(omemoManager, sender, message, information);
712            if (sender.equals(omemoManager.getOwnJid()) && decrypted != null) {
713                getOmemoStoreBackend().setDateOfLastReceivedMessage(omemoManager, sender);
714            }
715            return decrypted;
716        }
717
718        LOGGER.log(Level.INFO, "There is no key with our deviceId. Silently discard the message.");
719        return null;
720    }
721
722    /**
723     * Decrypt a given OMEMO encrypted message. Return null, if there is no OMEMO element in the message,
724     * otherwise try to decrypt the message and return a ClearTextMessage object.
725     *
726     * @param omemoManager omemoManager of the receiving device
727     * @param sender barejid of the sender
728     * @param message encrypted message
729     * @return decrypted message or null
730     * @throws InterruptedException                 Exception
731     * @throws SmackException.NoResponseException   Exception
732     * @throws SmackException.NotConnectedException Exception
733     * @throws CryptoFailedException                When the message could not be decrypted.
734     * @throws XMPPException.XMPPErrorException     Exception
735     * @throws CorruptedOmemoKeyException           When the used OMEMO keys are invalid.
736     * @throws NoRawSessionException                When there is no session to decrypt the message with in the double
737     *                                              ratchet library
738     */
739    ClearTextMessage processLocalMessage(OmemoManager omemoManager, BareJid sender, Message message) throws InterruptedException, SmackException.NoResponseException, SmackException.NotConnectedException, CryptoFailedException, XMPPException.XMPPErrorException, CorruptedOmemoKeyException, NoRawSessionException {
740        if (OmemoManager.stanzaContainsOmemoElement(message)) {
741            OmemoElement omemoMessageElement = message.getExtension(OmemoElement.ENCRYPTED, OMEMO_NAMESPACE_V_AXOLOTL);
742            OmemoMessageInformation info = new OmemoMessageInformation();
743            Message decrypted = processReceivingMessage(omemoManager,
744                    new OmemoDevice(sender, omemoMessageElement.getHeader().getSid()),
745                    omemoMessageElement, info);
746            return new ClearTextMessage(decrypted != null ? decrypted.getBody() : null, message, info);
747        } else {
748            LOGGER.log(Level.WARNING, "Stanza does not contain an OMEMO message.");
749            return null;
750        }
751    }
752
753    /**
754     * Encrypt a clear text message for the given recipient.
755     * The body of the message will be encrypted.
756     *
757     * @param omemoManager omemoManager of the sending device
758     * @param recipient BareJid of the recipient
759     * @param message   message to encrypt.
760     * @return OmemoMessageElement
761     * @throws CryptoFailedException
762     * @throws UndecidedOmemoIdentityException
763     * @throws NoSuchAlgorithmException
764     */
765    OmemoVAxolotlElement processSendingMessage(OmemoManager omemoManager, BareJid recipient, Message message)
766            throws CryptoFailedException, UndecidedOmemoIdentityException, NoSuchAlgorithmException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, CannotEstablishOmemoSessionException {
767        ArrayList<BareJid> recipients = new ArrayList<>();
768        recipients.add(recipient);
769        return processSendingMessage(omemoManager, recipients, message);
770    }
771
772    /**
773     * Encrypt a clear text message for the given recipients.
774     * The body of the message will be encrypted.
775     *
776     * @param omemoManager omemoManager of the sending device.
777     * @param recipients List of BareJids of all recipients
778     * @param message    message to encrypt.
779     * @return OmemoMessageElement
780     * @throws CryptoFailedException
781     * @throws UndecidedOmemoIdentityException
782     * @throws NoSuchAlgorithmException
783     */
784    OmemoVAxolotlElement processSendingMessage(OmemoManager omemoManager, ArrayList<BareJid> recipients, Message message)
785            throws CryptoFailedException, UndecidedOmemoIdentityException, NoSuchAlgorithmException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, CannotEstablishOmemoSessionException {
786
787        CannotEstablishOmemoSessionException sessionException = null;
788        // Them - The contact wants to read the message on all their devices.
789        HashMap<BareJid, ArrayList<OmemoDevice>> receivers = new HashMap<>();
790        for (BareJid recipient : recipients) {
791            try {
792                buildOrCreateOmemoSessionsFromBundles(omemoManager, recipient);
793            } catch (CannotEstablishOmemoSessionException e) {
794
795                if (sessionException == null) {
796                    sessionException = e;
797                } else {
798                    sessionException.addFailures(e);
799                }
800            }
801        }
802
803        for (BareJid recipient : recipients) {
804            CachedDeviceList theirDevices = getOmemoStoreBackend().loadCachedDeviceList(omemoManager, recipient);
805            ArrayList<OmemoDevice> receivingDevices = new ArrayList<>();
806            for (int id : theirDevices.getActiveDevices()) {
807                OmemoDevice recipientDevice = new OmemoDevice(recipient, id);
808
809                if (getOmemoStoreBackend().containsRawSession(omemoManager, recipientDevice)) {
810                    receivingDevices.add(recipientDevice);
811                }
812
813                if (sessionException != null) {
814                    sessionException.addSuccess(recipientDevice);
815                }
816            }
817
818            if (!receivingDevices.isEmpty()) {
819                receivers.put(recipient, receivingDevices);
820            }
821        }
822
823        // Us - We want to read the message on all of our devices
824        CachedDeviceList ourDevices = getOmemoStoreBackend().loadCachedDeviceList(omemoManager, omemoManager.getOwnJid());
825        if (ourDevices == null) {
826            ourDevices = new CachedDeviceList();
827        }
828
829        ArrayList<OmemoDevice> ourReceivingDevices = new ArrayList<>();
830        for (int id : ourDevices.getActiveDevices()) {
831            OmemoDevice ourDevice = new OmemoDevice(omemoManager.getOwnJid(), id);
832            if (id == omemoManager.getDeviceId()) {
833                // Don't build session with our exact device.
834                continue;
835            }
836
837            Date lastReceived = getOmemoStoreBackend().getDateOfLastReceivedMessage(omemoManager, ourDevice);
838            if (lastReceived == null) {
839                getOmemoStoreBackend().setDateOfLastReceivedMessage(omemoManager, ourDevice);
840                lastReceived = new Date();
841            }
842
843            if (OmemoConfiguration.getIgnoreStaleDevices() && System.currentTimeMillis() - lastReceived.getTime()
844                    > 1000L * 60 * 60 * OmemoConfiguration.getIgnoreStaleDevicesAfterHours()) {
845                LOGGER.log(Level.WARNING, "Refusing to encrypt message for stale device " + ourDevice +
846                        " which was inactive for at least " + OmemoConfiguration.getIgnoreStaleDevicesAfterHours() +
847                        " hours.");
848            } else {
849                if (getOmemoStoreBackend().containsRawSession(omemoManager, ourDevice)) {
850                    ourReceivingDevices.add(ourDevice);
851                }
852            }
853        }
854
855        if (!ourReceivingDevices.isEmpty()) {
856            receivers.put(omemoManager.getOwnJid(), ourReceivingDevices);
857        }
858
859        if (sessionException != null && sessionException.requiresThrowing()) {
860            throw sessionException;
861        }
862
863        return encryptOmemoMessage(omemoManager, receivers, message);
864    }
865
866    /**
867     * Decrypt a incoming OmemoMessageElement that was sent by the OmemoDevice 'from'.
868     *
869     * @param omemoManager omemoManager of the decrypting device.
870     * @param from          OmemoDevice that sent the message
871     * @param message       Encrypted OmemoMessageElement
872     * @param information   OmemoMessageInformation object which will contain metadata about the encryption
873     * @return Decrypted message
874     * @throws CryptoFailedException when decrypting message fails for some reason
875     * @throws InterruptedException
876     * @throws CorruptedOmemoKeyException
877     * @throws XMPPException.XMPPErrorException
878     * @throws SmackException.NotConnectedException
879     * @throws SmackException.NoResponseException
880     * @throws NoRawSessionException
881     */
882    private Message decryptOmemoMessageElement(OmemoManager omemoManager, OmemoDevice from, OmemoElement message,
883                                               final OmemoMessageInformation information)
884            throws CryptoFailedException, InterruptedException, CorruptedOmemoKeyException, XMPPException.XMPPErrorException,
885            SmackException.NotConnectedException, SmackException.NoResponseException, NoRawSessionException {
886
887        CipherAndAuthTag transportedKey = decryptTransportedOmemoKey(omemoManager, from, message, information);
888        return OmemoSession.decryptMessageElement(message, transportedKey);
889    }
890
891    /**
892     * Decrypt a messageKey that was transported in an OmemoElement.
893     *
894     * @param omemoManager  omemoManager of the receiving device.
895     * @param sender        omemoDevice of the sender.
896     * @param omemoMessage  omemoElement containing the key.
897     * @param messageInfo   omemoMessageInformation that will contain metadata about the encryption.
898     * @return a CipherAndAuthTag pair
899     * @throws CryptoFailedException
900     * @throws NoRawSessionException
901     * @throws InterruptedException
902     * @throws CorruptedOmemoKeyException
903     * @throws XMPPException.XMPPErrorException
904     * @throws SmackException.NotConnectedException
905     * @throws SmackException.NoResponseException
906     */
907    private CipherAndAuthTag decryptTransportedOmemoKey(OmemoManager omemoManager, OmemoDevice  sender,
908                                                        OmemoElement omemoMessage,
909                                                        OmemoMessageInformation messageInfo)
910            throws CryptoFailedException, NoRawSessionException, InterruptedException, CorruptedOmemoKeyException,
911            XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException {
912
913        int preKeyCountBefore = getOmemoStoreBackend().loadOmemoPreKeys(omemoManager).size();
914
915        OmemoSession<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
916                session = getOmemoStoreBackend().getOmemoSessionOf(omemoManager, sender);
917        CipherAndAuthTag cipherAndAuthTag = session.decryptTransportedKey(omemoMessage, omemoManager.getDeviceId());
918
919        messageInfo.setSenderDevice(sender);
920        messageInfo.setSenderIdentityKey(new IdentityKeyWrapper(session.getIdentityKey()));
921
922        if (preKeyCountBefore != getOmemoStoreBackend().loadOmemoPreKeys(omemoManager).size()) {
923            LOGGER.log(Level.INFO, "We used up a preKey. Publish new Bundle.");
924            publishBundle(omemoManager);
925        }
926        return cipherAndAuthTag;
927    }
928
929    /**
930     * Encrypt the message and return it as an OmemoMessageElement.
931     *
932     * @param omemoManager omemoManager of the encrypting device.
933     * @param recipients List of devices that will be able to decipher the message.
934     * @param message   Clear text message
935     *
936     * @throws CryptoFailedException when some cryptographic function fails
937     * @throws UndecidedOmemoIdentityException when the identity of one or more contacts is undecided
938     *
939     * @return OmemoMessageElement
940     */
941    OmemoVAxolotlElement encryptOmemoMessage(OmemoManager omemoManager, HashMap<BareJid, ArrayList<OmemoDevice>> recipients, Message message)
942            throws CryptoFailedException, UndecidedOmemoIdentityException {
943
944        OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
945                builder;
946        try {
947            builder = new OmemoMessageBuilder<>(omemoManager, getOmemoStoreBackend(), message.getBody());
948        } catch (UnsupportedEncodingException | BadPaddingException | IllegalBlockSizeException | NoSuchProviderException |
949                NoSuchPaddingException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException e) {
950            throw new CryptoFailedException(e);
951        }
952
953        UndecidedOmemoIdentityException undecided = null;
954
955        for (Map.Entry<BareJid, ArrayList<OmemoDevice>> entry : recipients.entrySet()) {
956            for (OmemoDevice c : entry.getValue()) {
957                try {
958                    builder.addRecipient(c);
959                } catch (CorruptedOmemoKeyException e) {
960                    // TODO: How to react?
961                    LOGGER.log(Level.SEVERE, "encryptOmemoMessage failed to establish a session with device "
962                            + c + ": " + e.getMessage());
963                } catch (UndecidedOmemoIdentityException e) {
964                    // Collect all undecided devices
965                    if (undecided == null) {
966                        undecided = e;
967                    } else {
968                        undecided.join(e);
969                    }
970                }
971            }
972        }
973
974        if (undecided != null) {
975            throw undecided;
976        }
977
978        return builder.finish();
979    }
980
981    /**
982     * Prepares a keyTransportElement with a random aes key and iv.
983     *
984     * @param omemoManager omemoManager of the sending device.
985     * @param recipients recipients of the omemoKeyTransportElement
986     * @return KeyTransportElement
987     * @throws CryptoFailedException
988     * @throws UndecidedOmemoIdentityException
989     * @throws CorruptedOmemoKeyException
990     * @throws CannotEstablishOmemoSessionException
991     */
992    OmemoVAxolotlElement prepareOmemoKeyTransportElement(OmemoManager omemoManager, OmemoDevice... recipients) throws CryptoFailedException,
993            UndecidedOmemoIdentityException, CorruptedOmemoKeyException, CannotEstablishOmemoSessionException {
994
995        OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
996                builder;
997        try {
998            builder = new OmemoMessageBuilder<>(omemoManager, getOmemoStoreBackend(), null);
999
1000        } catch (UnsupportedEncodingException | BadPaddingException | IllegalBlockSizeException | NoSuchProviderException |
1001                NoSuchPaddingException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException e) {
1002            throw new CryptoFailedException(e);
1003        }
1004
1005        for (OmemoDevice r : recipients) {
1006            builder.addRecipient(r);
1007        }
1008
1009        return builder.finish();
1010    }
1011
1012    /**
1013     * Prepare a KeyTransportElement with aesKey and iv.
1014     *
1015     * @param omemoManager  OmemoManager of the sending device.
1016     * @param aesKey        AES key
1017     * @param iv            initialization vector
1018     * @param recipients    recipients
1019     * @return              KeyTransportElement
1020     * @throws CryptoFailedException
1021     * @throws UndecidedOmemoIdentityException
1022     * @throws CorruptedOmemoKeyException
1023     * @throws CannotEstablishOmemoSessionException
1024     */
1025    OmemoVAxolotlElement prepareOmemoKeyTransportElement(OmemoManager omemoManager, byte[] aesKey, byte[] iv, OmemoDevice... recipients) throws CryptoFailedException,
1026            UndecidedOmemoIdentityException, CorruptedOmemoKeyException, CannotEstablishOmemoSessionException {
1027
1028        OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
1029                builder;
1030        try {
1031            builder = new OmemoMessageBuilder<>(omemoManager, getOmemoStoreBackend(), aesKey, iv);
1032
1033        } catch (UnsupportedEncodingException | BadPaddingException | IllegalBlockSizeException | NoSuchProviderException |
1034                NoSuchPaddingException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException e) {
1035            throw new CryptoFailedException(e);
1036        }
1037
1038        for (OmemoDevice r : recipients) {
1039            builder.addRecipient(r);
1040        }
1041
1042        return builder.finish();
1043    }
1044
1045    /**
1046     * Return a new RatchetUpdateMessage.
1047     *
1048     * @param omemoManager  omemoManager of the sending device.
1049     * @param recipient     recipient
1050     * @param preKeyMessage if true, a new session will be built for this message (useful to repair broken sessions)
1051     *                      otherwise the message will be encrypted using the existing session.
1052     * @return              OmemoRatchetUpdateMessage
1053     * @throws CannotEstablishOmemoSessionException
1054     * @throws CorruptedOmemoKeyException
1055     * @throws CryptoFailedException
1056     * @throws UndecidedOmemoIdentityException
1057     */
1058    protected Message getOmemoRatchetUpdateMessage(OmemoManager omemoManager, OmemoDevice recipient, boolean preKeyMessage) throws CannotEstablishOmemoSessionException, CorruptedOmemoKeyException, CryptoFailedException, UndecidedOmemoIdentityException {
1059        if (preKeyMessage) {
1060            buildSessionFromOmemoBundle(omemoManager, recipient, true);
1061        }
1062
1063        OmemoVAxolotlElement keyTransportElement = prepareOmemoKeyTransportElement(omemoManager, recipient);
1064        Message ratchetUpdateMessage = omemoManager.finishMessage(keyTransportElement);
1065        ratchetUpdateMessage.setTo(recipient.getJid());
1066
1067        return ratchetUpdateMessage;
1068    }
1069
1070    /**
1071     * Send an OmemoRatchetUpdateMessage to recipient. If preKeyMessage is true, the message will be encrypted using a
1072     * freshly built session. This can be used to repair broken sessions.
1073     *
1074     * @param omemoManager      omemoManager of the sending device.
1075     * @param recipient         recipient
1076     * @param preKeyMessage     shall this be a preKeyMessage?
1077     * @throws UndecidedOmemoIdentityException
1078     * @throws CorruptedOmemoKeyException
1079     * @throws CryptoFailedException
1080     * @throws CannotEstablishOmemoSessionException
1081     */
1082    protected void sendOmemoRatchetUpdateMessage(OmemoManager omemoManager, OmemoDevice recipient, boolean preKeyMessage) throws UndecidedOmemoIdentityException, CorruptedOmemoKeyException, CryptoFailedException, CannotEstablishOmemoSessionException {
1083        Message ratchetUpdateMessage = getOmemoRatchetUpdateMessage(omemoManager, recipient, preKeyMessage);
1084
1085        try {
1086            omemoManager.getConnection().sendStanza(ratchetUpdateMessage);
1087
1088        } catch (SmackException.NotConnectedException | InterruptedException e) {
1089            LOGGER.log(Level.WARNING, "sendOmemoRatchetUpdateMessage failed: " + e.getMessage());
1090        }
1091    }
1092
1093    /**
1094     * Listen for incoming messages and carbons, decrypt them and pass the cleartext messages to the registered
1095     * OmemoMessageListeners.
1096     *
1097     * @param omemoManager omemoManager we want to register with
1098     */
1099    private void registerOmemoMessageStanzaListeners(OmemoManager omemoManager) {
1100        omemoManager.getConnection().removeAsyncStanzaListener(omemoManager.getOmemoStanzaListener());
1101        omemoManager.getConnection().addAsyncStanzaListener(omemoManager.getOmemoStanzaListener(), omemoStanzaFilter);
1102
1103        CarbonManager.getInstanceFor(omemoManager.getConnection()).removeCarbonCopyReceivedListener(omemoManager.getOmemoCarbonCopyListener());
1104        CarbonManager.getInstanceFor(omemoManager.getConnection()).addCarbonCopyReceivedListener(omemoManager.getOmemoCarbonCopyListener());
1105    }
1106
1107    /**
1108     * StanzaFilter that filters messages containing a OMEMO element.
1109     */
1110    private final StanzaFilter omemoStanzaFilter = new StanzaFilter() {
1111        @Override
1112        public boolean accept(Stanza stanza) {
1113            return stanza instanceof Message && OmemoManager.stanzaContainsOmemoElement(stanza);
1114        }
1115    };
1116
1117    /**
1118     * Try to decrypt a mamQueryResult. Note that OMEMO messages can only be decrypted once on a device, so if you
1119     * try to decrypt a message that has been decrypted earlier in time, the decryption will fail. You should handle
1120     * message history locally when using OMEMO, since you cannot rely on MAM.
1121     *
1122     * @param omemoManager omemoManager of the decrypting device.
1123     * @param mamQueryResult mamQueryResult that shall be decrypted.
1124     * @return list of decrypted messages.
1125     * @throws InterruptedException
1126     * @throws XMPPException.XMPPErrorException
1127     * @throws SmackException.NotConnectedException
1128     * @throws SmackException.NoResponseException
1129     */
1130    List<ClearTextMessage> decryptMamQueryResult(OmemoManager omemoManager, MamManager.MamQueryResult mamQueryResult)
1131            throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException {
1132        List<ClearTextMessage> result = new ArrayList<>();
1133        for (Forwarded f : mamQueryResult.forwardedMessages) {
1134            if (OmemoManager.stanzaContainsOmemoElement(f.getForwardedStanza())) {
1135                // Decrypt OMEMO messages
1136                try {
1137                    result.add(processLocalMessage(omemoManager, f.getForwardedStanza().getFrom().asBareJid(), (Message) f.getForwardedStanza()));
1138                } catch (NoRawSessionException | CorruptedOmemoKeyException | CryptoFailedException e) {
1139                    LOGGER.log(Level.WARNING, "decryptMamQueryResult failed to decrypt message from "
1140                            + f.getForwardedStanza().getFrom() + " due to corrupted session/key: " + e.getMessage());
1141                }
1142            } else {
1143                // Wrap cleartext messages
1144                Message m = (Message) f.getForwardedStanza();
1145                result.add(new ClearTextMessage(m.getBody(), m,
1146                        new OmemoMessageInformation(null, null, OmemoMessageInformation.CARBON.NONE, false)));
1147            }
1148        }
1149        return result;
1150    }
1151
1152    /**
1153     * Return the barejid of the user that sent the message inside the MUC. If the message wasn't sent in a MUC,
1154     * return null;
1155     *
1156     * @param omemoManager omemoManager
1157     * @param stanza message
1158     * @return BareJid of the sender.
1159     */
1160    private static OmemoDevice getSender(OmemoManager omemoManager, Stanza stanza) {
1161        OmemoElement omemoElement = stanza.getExtension(OmemoElement.ENCRYPTED, OMEMO_NAMESPACE_V_AXOLOTL);
1162        Jid sender = stanza.getFrom();
1163        if (isMucMessage(omemoManager, stanza)) {
1164            MultiUserChatManager mucm = MultiUserChatManager.getInstanceFor(omemoManager.getConnection());
1165            MultiUserChat muc = mucm.getMultiUserChat(sender.asEntityBareJidIfPossible());
1166            sender = muc.getOccupant(sender.asEntityFullJidIfPossible()).getJid().asBareJid();
1167        }
1168        if (sender == null) {
1169            throw new AssertionError("Sender is null.");
1170        }
1171        return new OmemoDevice(sender.asBareJid(), omemoElement.getHeader().getSid());
1172    }
1173
1174    /**
1175     * Return true, if the user knows a multiUserChat with a jid matching the sender of the stanza.
1176     * @param omemoManager  omemoManager of the user
1177     * @param stanza        stanza in question
1178     * @return              true if MUC message, otherwise false.
1179     */
1180    private static boolean isMucMessage(OmemoManager omemoManager, Stanza stanza) {
1181        BareJid sender = stanza.getFrom().asBareJid();
1182        MultiUserChatManager mucm = MultiUserChatManager.getInstanceFor(omemoManager.getConnection());
1183
1184        return mucm.getJoinedRooms().contains(sender.asEntityBareJidIfPossible());
1185    }
1186
1187    OmemoStanzaListener createStanzaListener(OmemoManager omemoManager) {
1188        return new OmemoStanzaListener(omemoManager, this);
1189    }
1190
1191    /**
1192     * StanzaListener that listens for incoming omemoElements that are NOT send via carbons.
1193     */
1194    class OmemoStanzaListener implements StanzaListener {
1195        private final OmemoManager omemoManager;
1196        private final OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
1197                service;
1198
1199        OmemoStanzaListener(OmemoManager omemoManager,
1200                            OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> service) {
1201            this.omemoManager = omemoManager;
1202            this.service = service;
1203        }
1204
1205        @Override
1206        public void processStanza(Stanza stanza) throws SmackException.NotConnectedException, InterruptedException {
1207            Message decrypted;
1208            OmemoElement omemoMessage = stanza.getExtension(OmemoElement.ENCRYPTED, OMEMO_NAMESPACE_V_AXOLOTL);
1209            OmemoMessageInformation messageInfo = new OmemoMessageInformation();
1210            MultiUserChatManager mucm = MultiUserChatManager.getInstanceFor(omemoManager.getConnection());
1211            OmemoDevice senderDevice = getSender(omemoManager, stanza);
1212            try {
1213                // Is it a MUC message...
1214                if (isMucMessage(omemoManager, stanza)) {
1215
1216                    MultiUserChat muc = mucm.getMultiUserChat(stanza.getFrom().asEntityBareJidIfPossible());
1217                    if (omemoMessage.isMessageElement()) {
1218
1219                        decrypted = processReceivingMessage(omemoManager, senderDevice, omemoMessage, messageInfo);
1220                        if (decrypted != null) {
1221                            omemoManager.notifyOmemoMucMessageReceived(muc, senderDevice.getJid(), decrypted.getBody(),
1222                                    (Message) stanza, null, messageInfo);
1223                        }
1224
1225                    } else if (omemoMessage.isKeyTransportElement()) {
1226
1227                        CipherAndAuthTag cipherAndAuthTag = decryptTransportedOmemoKey(omemoManager, senderDevice, omemoMessage, messageInfo);
1228                        if (cipherAndAuthTag != null) {
1229                            omemoManager.notifyOmemoMucKeyTransportMessageReceived(muc, senderDevice.getJid(), cipherAndAuthTag,
1230                                    (Message) stanza, null, messageInfo);
1231                        }
1232                    }
1233                }
1234                // ... or a normal chat message...
1235                else {
1236                    if (omemoMessage.isMessageElement()) {
1237
1238                        decrypted = service.processReceivingMessage(omemoManager, senderDevice, omemoMessage, messageInfo);
1239                        if (decrypted != null) {
1240                            omemoManager.notifyOmemoMessageReceived(decrypted.getBody(), (Message) stanza, null, messageInfo);
1241                        }
1242
1243                    } else if (omemoMessage.isKeyTransportElement()) {
1244
1245                        CipherAndAuthTag cipherAndAuthTag = decryptTransportedOmemoKey(omemoManager, senderDevice, omemoMessage, messageInfo);
1246                        if (cipherAndAuthTag != null) {
1247                            omemoManager.notifyOmemoKeyTransportMessageReceived(cipherAndAuthTag, (Message) stanza, null, messageInfo);
1248                        }
1249                    }
1250                }
1251
1252            } catch (CryptoFailedException | CorruptedOmemoKeyException | InterruptedException | SmackException.NotConnectedException | XMPPException.XMPPErrorException | SmackException.NoResponseException e) {
1253                LOGGER.log(Level.WARNING, "internal omemoMessageListener failed to decrypt incoming OMEMO message: "
1254                        + e.getMessage());
1255
1256            } catch (NoRawSessionException e) {
1257                try {
1258                    LOGGER.log(Level.INFO, "Received message with invalid session from " +
1259                            senderDevice + ". Send RatchetUpdateMessage.");
1260                    service.sendOmemoRatchetUpdateMessage(omemoManager, senderDevice, true);
1261
1262                } catch (UndecidedOmemoIdentityException | CorruptedOmemoKeyException | CannotEstablishOmemoSessionException | CryptoFailedException e1) {
1263                    LOGGER.log(Level.WARNING, "internal omemoMessageListener failed to establish a session for incoming OMEMO message: "
1264                            + e.getMessage());
1265                }
1266            }
1267        }
1268    }
1269
1270    OmemoCarbonCopyListener createOmemoCarbonCopyListener(OmemoManager omemoManager) {
1271        return new OmemoCarbonCopyListener(omemoManager, this, omemoStanzaFilter);
1272    }
1273
1274    /**
1275     * StanzaListener that listens for incoming OmemoElements that ARE sent in carbons.
1276     */
1277    class OmemoCarbonCopyListener implements CarbonCopyReceivedListener {
1278
1279        private final OmemoManager omemoManager;
1280        private final OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> service;
1281        private final StanzaFilter filter;
1282
1283        public OmemoCarbonCopyListener(OmemoManager omemoManager,
1284                                       OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> service,
1285                                       StanzaFilter filter) {
1286            this.omemoManager = omemoManager;
1287            this.service = service;
1288            this.filter = filter;
1289        }
1290
1291        @Override
1292        public void onCarbonCopyReceived(CarbonExtension.Direction direction, Message carbonCopy, Message wrappingMessage) {
1293            if (filter.accept(carbonCopy)) {
1294                final OmemoDevice senderDevice = getSender(omemoManager, carbonCopy);
1295                Message decrypted;
1296                MultiUserChatManager mucm = MultiUserChatManager.getInstanceFor(omemoManager.getConnection());
1297                OmemoElement omemoMessage = carbonCopy.getExtension(OmemoElement.ENCRYPTED, OMEMO_NAMESPACE_V_AXOLOTL);
1298                OmemoMessageInformation messageInfo = new OmemoMessageInformation();
1299
1300                if (CarbonExtension.Direction.received.equals(direction)) {
1301                    messageInfo.setCarbon(OmemoMessageInformation.CARBON.RECV);
1302                } else {
1303                    messageInfo.setCarbon(OmemoMessageInformation.CARBON.SENT);
1304                }
1305
1306                try {
1307                    // Is it a MUC message...
1308                    if (isMucMessage(omemoManager, carbonCopy)) {
1309
1310                        MultiUserChat muc = mucm.getMultiUserChat(carbonCopy.getFrom().asEntityBareJidIfPossible());
1311                        if (omemoMessage.isMessageElement()) {
1312
1313                            decrypted = processReceivingMessage(omemoManager, senderDevice, omemoMessage, messageInfo);
1314                            if (decrypted != null) {
1315                                omemoManager.notifyOmemoMucMessageReceived(muc, senderDevice.getJid(), decrypted.getBody(),
1316                                        carbonCopy, wrappingMessage, messageInfo);
1317                            }
1318
1319                        } else if (omemoMessage.isKeyTransportElement()) {
1320
1321                            CipherAndAuthTag cipherAndAuthTag = decryptTransportedOmemoKey(omemoManager, senderDevice, omemoMessage, messageInfo);
1322                            if (cipherAndAuthTag != null) {
1323                                omemoManager.notifyOmemoMucKeyTransportMessageReceived(muc, senderDevice.getJid(), cipherAndAuthTag,
1324                                        carbonCopy, wrappingMessage, messageInfo);
1325                            }
1326                        }
1327                    }
1328                    // ... or a normal chat message...
1329                    else {
1330                        if (omemoMessage.isMessageElement()) {
1331
1332                            decrypted = service.processReceivingMessage(omemoManager, senderDevice, omemoMessage, messageInfo);
1333                            if (decrypted != null) {
1334                                omemoManager.notifyOmemoMessageReceived(decrypted.getBody(), carbonCopy, null, messageInfo);
1335                            }
1336
1337                        } else if (omemoMessage.isKeyTransportElement()) {
1338
1339                            CipherAndAuthTag cipherAndAuthTag = decryptTransportedOmemoKey(omemoManager, senderDevice, omemoMessage, messageInfo);
1340                            if (cipherAndAuthTag != null) {
1341                                omemoManager.notifyOmemoKeyTransportMessageReceived(cipherAndAuthTag, carbonCopy, null, messageInfo);
1342                            }
1343                        }
1344                    }
1345
1346                } catch (CryptoFailedException | CorruptedOmemoKeyException | InterruptedException | SmackException.NotConnectedException | XMPPException.XMPPErrorException | SmackException.NoResponseException e) {
1347                    LOGGER.log(Level.WARNING, "internal omemoMessageListener failed to decrypt incoming OMEMO carbon copy: "
1348                            + e.getMessage());
1349
1350                } catch (final NoRawSessionException e) {
1351                    Async.go(new Runnable() {
1352                        @Override
1353                        public void run() {
1354                            try {
1355                                LOGGER.log(Level.INFO, "Received OMEMO carbon copy message with invalid session from " +
1356                                        senderDevice + ". Send RatchetUpdateMessage.");
1357                                service.sendOmemoRatchetUpdateMessage(omemoManager, senderDevice, true);
1358
1359                            } catch (UndecidedOmemoIdentityException | CorruptedOmemoKeyException | CannotEstablishOmemoSessionException | CryptoFailedException e1) {
1360                                LOGGER.log(Level.WARNING, "internal omemoMessageListener failed to establish a session for incoming OMEMO carbon message: "
1361                                        + e.getMessage());
1362                            }
1363                        }
1364                    });
1365
1366                }
1367            }
1368        }
1369    }
1370}