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