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