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.BODY_OMEMO_HINT;
020import static org.jivesoftware.smackx.omemo.util.OmemoConstants.OMEMO_NAMESPACE_V_AXOLOTL;
021import static org.jivesoftware.smackx.omemo.util.OmemoConstants.PEP_NODE_DEVICE_LIST_NOTIFY;
022
023import java.security.NoSuchAlgorithmException;
024import java.util.ArrayList;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.List;
028import java.util.Random;
029import java.util.Set;
030import java.util.WeakHashMap;
031import java.util.logging.Level;
032import java.util.logging.Logger;
033
034import org.jivesoftware.smack.AbstractConnectionListener;
035import org.jivesoftware.smack.AbstractXMPPConnection;
036import org.jivesoftware.smack.Manager;
037import org.jivesoftware.smack.SmackException;
038import org.jivesoftware.smack.XMPPConnection;
039import org.jivesoftware.smack.XMPPException;
040import org.jivesoftware.smack.packet.ExtensionElement;
041import org.jivesoftware.smack.packet.Message;
042import org.jivesoftware.smack.packet.Stanza;
043import org.jivesoftware.smack.util.Async;
044import org.jivesoftware.smackx.carbons.CarbonManager;
045import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
046import org.jivesoftware.smackx.eme.element.ExplicitMessageEncryptionElement;
047import org.jivesoftware.smackx.hints.element.StoreHint;
048import org.jivesoftware.smackx.mam.MamManager;
049import org.jivesoftware.smackx.muc.MultiUserChat;
050import org.jivesoftware.smackx.muc.MultiUserChatManager;
051import org.jivesoftware.smackx.muc.RoomInfo;
052import org.jivesoftware.smackx.omemo.element.OmemoDeviceListVAxolotlElement;
053import org.jivesoftware.smackx.omemo.element.OmemoElement;
054import org.jivesoftware.smackx.omemo.element.OmemoVAxolotlElement;
055import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException;
056import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
057import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
058import org.jivesoftware.smackx.omemo.exceptions.NoOmemoSupportException;
059import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException;
060import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException;
061import org.jivesoftware.smackx.omemo.internal.CachedDeviceList;
062import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
063import org.jivesoftware.smackx.omemo.internal.ClearTextMessage;
064import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
065import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation;
066import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener;
067import org.jivesoftware.smackx.omemo.listener.OmemoMucMessageListener;
068import org.jivesoftware.smackx.pep.PEPListener;
069import org.jivesoftware.smackx.pep.PEPManager;
070import org.jivesoftware.smackx.pubsub.EventElement;
071import org.jivesoftware.smackx.pubsub.ItemsExtension;
072import org.jivesoftware.smackx.pubsub.PayloadItem;
073import org.jivesoftware.smackx.pubsub.PubSubException;
074import org.jivesoftware.smackx.pubsub.packet.PubSub;
075
076import org.jxmpp.jid.BareJid;
077import org.jxmpp.jid.DomainBareJid;
078import org.jxmpp.jid.EntityBareJid;
079import org.jxmpp.jid.EntityFullJid;
080import org.jxmpp.jid.impl.JidCreate;
081import org.jxmpp.stringprep.XmppStringprepException;
082
083/**
084 * Manager that allows sending messages encrypted with OMEMO.
085 * This class also provides some methods useful for a client that implements OMEMO.
086 *
087 * @author Paul Schaub
088 */
089
090public final class OmemoManager extends Manager {
091    private static final Logger LOGGER = Logger.getLogger(OmemoManager.class.getName());
092
093    private static final WeakHashMap<XMPPConnection, WeakHashMap<Integer,OmemoManager>> INSTANCES = new WeakHashMap<>();
094    private final OmemoService<?, ?, ?, ?, ?, ?, ?, ?, ?> service;
095
096    private final HashSet<OmemoMessageListener> omemoMessageListeners = new HashSet<>();
097    private final HashSet<OmemoMucMessageListener> omemoMucMessageListeners = new HashSet<>();
098
099    private OmemoService<?,?,?,?,?,?,?,?,?>.OmemoStanzaListener omemoStanzaListener;
100    private OmemoService<?,?,?,?,?,?,?,?,?>.OmemoCarbonCopyListener omemoCarbonCopyListener;
101
102    private int deviceId;
103
104    /**
105     * Private constructor to prevent multiple instances on a single connection (which probably would be bad!).
106     *
107     * @param connection connection
108     */
109    private OmemoManager(XMPPConnection connection, int deviceId) {
110        super(connection);
111
112        this.deviceId = deviceId;
113
114        connection.addConnectionListener(new AbstractConnectionListener() {
115            @Override
116            public void authenticated(XMPPConnection connection, boolean resumed) {
117                if (resumed) {
118                    return;
119                }
120                Async.go(new Runnable() {
121                    @Override
122                    public void run() {
123                        try {
124                            initialize();
125                        } catch (InterruptedException | CorruptedOmemoKeyException | PubSubException.NotALeafNodeException | SmackException.NotLoggedInException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) {
126                            LOGGER.log(Level.SEVERE, "connectionListener.authenticated() failed to initialize OmemoManager: "
127                                    + e.getMessage());
128                        }
129                    }
130                });
131            }
132        });
133
134        PEPManager.getInstanceFor(connection).addPEPListener(deviceListUpdateListener);
135        ServiceDiscoveryManager.getInstanceFor(connection).addFeature(PEP_NODE_DEVICE_LIST_NOTIFY);
136
137        service = OmemoService.getInstance();
138    }
139
140    /**
141     * Get an instance of the OmemoManager for the given connection and deviceId.
142     *
143     * @param connection Connection
144     * @param deviceId deviceId of the Manager. If the deviceId is null, a random id will be generated.
145     * @return an OmemoManager
146     */
147    public synchronized static OmemoManager getInstanceFor(XMPPConnection connection, Integer deviceId) {
148        WeakHashMap<Integer,OmemoManager> managersOfConnection = INSTANCES.get(connection);
149        if (managersOfConnection == null) {
150            managersOfConnection = new WeakHashMap<>();
151            INSTANCES.put(connection, managersOfConnection);
152        }
153
154        if (deviceId == null || deviceId < 1) {
155            deviceId = randomDeviceId();
156        }
157
158        OmemoManager manager = managersOfConnection.get(deviceId);
159        if (manager == null) {
160            manager = new OmemoManager(connection, deviceId);
161            managersOfConnection.put(deviceId, manager);
162        }
163        return manager;
164    }
165
166    /**
167     * Get an instance of the OmemoManager for the given connection.
168     * This method creates the OmemoManager for the stored defaultDeviceId of the connections user.
169     * If there is no such id is stored, it uses a fresh deviceId and sets that as defaultDeviceId instead.
170     *
171     * @param connection connection
172     * @return OmemoManager
173     */
174    public synchronized static OmemoManager getInstanceFor(XMPPConnection connection) {
175        BareJid user;
176        if (connection.getUser() != null) {
177            user = connection.getUser().asBareJid();
178        } else {
179            // This might be dangerous
180            try {
181                user = JidCreate.bareFrom(((AbstractXMPPConnection) connection).getConfiguration().getUsername());
182            } catch (XmppStringprepException e) {
183                throw new AssertionError("Username is not a valid Jid. " +
184                        "Use OmemoManager.gerInstanceFor(Connection, deviceId) instead.");
185            }
186        }
187
188        int defaultDeviceId = OmemoService.getInstance().getOmemoStoreBackend().getDefaultDeviceId(user);
189        if (defaultDeviceId < 1) {
190            defaultDeviceId = randomDeviceId();
191            OmemoService.getInstance().getOmemoStoreBackend().setDefaultDeviceId(user, defaultDeviceId);
192        }
193
194        return getInstanceFor(connection, defaultDeviceId);
195    }
196
197    /**
198     * Initializes the OmemoManager. This method is called automatically once the client logs into the server successfully.
199     *
200     * @throws CorruptedOmemoKeyException
201     * @throws InterruptedException
202     * @throws SmackException.NoResponseException
203     * @throws SmackException.NotConnectedException
204     * @throws XMPPException.XMPPErrorException
205     * @throws SmackException.NotLoggedInException
206     * @throws PubSubException.NotALeafNodeException
207     */
208    public void initialize() throws CorruptedOmemoKeyException, InterruptedException, SmackException.NoResponseException,
209            SmackException.NotConnectedException, XMPPException.XMPPErrorException, SmackException.NotLoggedInException,
210            PubSubException.NotALeafNodeException {
211        getOmemoService().initialize(this);
212    }
213
214    /**
215     * OMEMO encrypt a cleartext message for a single recipient.
216     *
217     * @param to recipients barejid
218     * @param message text to encrypt
219     * @return encrypted message
220     * @throws CryptoFailedException                when something crypto related fails
221     * @throws UndecidedOmemoIdentityException      When there are undecided devices
222     * @throws NoSuchAlgorithmException
223     * @throws InterruptedException
224     * @throws CannotEstablishOmemoSessionException when we could not create session withs all of the recipients devices.
225     * @throws SmackException.NotConnectedException
226     * @throws SmackException.NoResponseException
227     */
228    public Message encrypt(BareJid to, String message) throws CryptoFailedException, UndecidedOmemoIdentityException, NoSuchAlgorithmException, InterruptedException, CannotEstablishOmemoSessionException, SmackException.NotConnectedException, SmackException.NoResponseException {
229        Message m = new Message();
230        m.setBody(message);
231        OmemoVAxolotlElement encrypted = getOmemoService().processSendingMessage(this, to, m);
232        return finishMessage(encrypted);
233    }
234
235    /**
236     * OMEMO encrypt a cleartext message for multiple recipients.
237     *
238     * @param recipients recipients barejids
239     * @param message text to encrypt
240     * @return encrypted message.
241     * @throws CryptoFailedException    When something crypto related fails
242     * @throws UndecidedOmemoIdentityException  When there are undecided devices.
243     * @throws NoSuchAlgorithmException
244     * @throws InterruptedException
245     * @throws CannotEstablishOmemoSessionException When there is one recipient, for whom we failed to create a session
246     *                                              with every one of their devices.
247     * @throws SmackException.NotConnectedException
248     * @throws SmackException.NoResponseException
249     */
250    public Message encrypt(ArrayList<BareJid> recipients, String message) throws CryptoFailedException, UndecidedOmemoIdentityException, NoSuchAlgorithmException, InterruptedException, CannotEstablishOmemoSessionException, SmackException.NotConnectedException, SmackException.NoResponseException {
251        Message m = new Message();
252        m.setBody(message);
253        OmemoVAxolotlElement encrypted = getOmemoService().processSendingMessage(this, recipients, m);
254        return finishMessage(encrypted);
255    }
256
257    /**
258     * Encrypt a message for all recipients in the MultiUserChat.
259     *
260     * @param muc multiUserChat
261     * @param message message to send
262     * @return encrypted message
263     * @throws UndecidedOmemoIdentityException when there are undecided devices.
264     * @throws NoSuchAlgorithmException
265     * @throws CryptoFailedException
266     * @throws XMPPException.XMPPErrorException
267     * @throws SmackException.NotConnectedException
268     * @throws InterruptedException
269     * @throws SmackException.NoResponseException
270     * @throws NoOmemoSupportException When the muc doesn't support OMEMO.
271     * @throws CannotEstablishOmemoSessionException when there is a user for whom we could not create a session
272     *                                              with any of their devices.
273     */
274    public Message encrypt(MultiUserChat muc, String message) throws UndecidedOmemoIdentityException, NoSuchAlgorithmException, CryptoFailedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, NoOmemoSupportException, CannotEstablishOmemoSessionException {
275        if (!multiUserChatSupportsOmemo(muc.getRoom())) {
276            throw new NoOmemoSupportException();
277        }
278        Message m = new Message();
279        m.setBody(message);
280        ArrayList<BareJid> recipients = new ArrayList<>();
281        for (EntityFullJid e : muc.getOccupants()) {
282            recipients.add(muc.getOccupant(e).getJid().asBareJid());
283        }
284        return encrypt(recipients, message);
285    }
286
287    /**
288     * Encrypt a message for all users we could build a session with successfully in a previous attempt.
289     * This method can come in handy as a fallback when encrypting a message fails due to devices we cannot
290     * build a session with.
291     *
292     * @param exception CannotEstablishSessionException from a previous encrypt(user(s), message) call.
293     * @param message message we want to send.
294     * @return encrypted message
295     * @throws CryptoFailedException
296     * @throws UndecidedOmemoIdentityException when there are undecided identities.
297     */
298    public Message encryptForExistingSessions(CannotEstablishOmemoSessionException exception, String message) throws CryptoFailedException, UndecidedOmemoIdentityException {
299        Message m = new Message();
300        m.setBody(message);
301        OmemoVAxolotlElement encrypted = getOmemoService().encryptOmemoMessage(this, exception.getSuccesses(), m);
302        return finishMessage(encrypted);
303    }
304
305    /**
306     * Decrypt an OMEMO message. This method comes handy when dealing with messages that were not automatically
307     * decrypted by smack-omemo, eg. MAM query messages.
308     * @param sender sender of the message
309     * @param omemoMessage message
310     * @return decrypted message
311     * @throws InterruptedException                 Exception
312     * @throws SmackException.NoResponseException   Exception
313     * @throws SmackException.NotConnectedException Exception
314     * @throws CryptoFailedException                When decryption fails
315     * @throws XMPPException.XMPPErrorException     Exception
316     * @throws CorruptedOmemoKeyException           When the used keys are invalid
317     * @throws NoRawSessionException                When there is no double ratchet session found for this message
318     */
319    public ClearTextMessage decrypt(BareJid sender, Message omemoMessage) throws InterruptedException, SmackException.NoResponseException, SmackException.NotConnectedException, CryptoFailedException, XMPPException.XMPPErrorException, CorruptedOmemoKeyException, NoRawSessionException {
320        return getOmemoService().processLocalMessage(this, sender, omemoMessage);
321    }
322
323    /**
324     * Return a list of all OMEMO messages that were found in the MAM query result, that could be successfully decrypted.
325     * Normal cleartext messages are also added to this list.
326     *
327     * @param mamQueryResult mamQueryResult
328     * @return list of decrypted OmemoMessages
329     * @throws InterruptedException                 Exception
330     * @throws XMPPException.XMPPErrorException     Exception
331     * @throws SmackException.NotConnectedException Exception
332     * @throws SmackException.NoResponseException   Exception
333     */
334    public List<ClearTextMessage> decryptMamQueryResult(MamManager.MamQueryResult mamQueryResult) throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException {
335        List<ClearTextMessage> l = new ArrayList<>();
336        l.addAll(getOmemoService().decryptMamQueryResult(this, mamQueryResult));
337        return l;
338    }
339
340    /**
341     * Trust that a fingerprint belongs to an OmemoDevice.
342     * The fingerprint must be the lowercase, hexadecimal fingerprint of the identityKey of the device and must
343     * be of length 64.
344     * @param device device
345     * @param fingerprint fingerprint
346     */
347    public void trustOmemoIdentity(OmemoDevice device, OmemoFingerprint fingerprint) {
348        getOmemoService().getOmemoStoreBackend().trustOmemoIdentity(this, device, fingerprint);
349    }
350
351    /**
352     * Distrust the fingerprint/OmemoDevice tuple.
353     * The fingerprint must be the lowercase, hexadecimal fingerprint of the identityKey of the device and must
354     * be of length 64.
355     * @param device device
356     * @param fingerprint fingerprint
357     */
358    public void distrustOmemoIdentity(OmemoDevice device, OmemoFingerprint fingerprint) {
359        getOmemoService().getOmemoStoreBackend().distrustOmemoIdentity(this, device, fingerprint);
360    }
361
362    /**
363     * Returns true, if the fingerprint/OmemoDevice tuple is trusted, otherwise false.
364     * The fingerprint must be the lowercase, hexadecimal fingerprint of the identityKey of the device and must
365     * be of length 64.
366     * @param device device
367     * @param fingerprint fingerprint
368     * @return
369     */
370    public boolean isTrustedOmemoIdentity(OmemoDevice device, OmemoFingerprint fingerprint) {
371        return getOmemoService().getOmemoStoreBackend().isTrustedOmemoIdentity(this, device, fingerprint);
372    }
373
374    /**
375     * Returns true, if the fingerprint/OmemoDevice tuple is decided by the user.
376     * The fingerprint must be the lowercase, hexadecimal fingerprint of the identityKey of the device and must
377     * be of length 64.
378     * @param device device
379     * @param fingerprint fingerprint
380     * @return
381     */
382    public boolean isDecidedOmemoIdentity(OmemoDevice device, OmemoFingerprint fingerprint) {
383        return getOmemoService().getOmemoStoreBackend().isDecidedOmemoIdentity(this, device, fingerprint);
384    }
385
386    /**
387     * Clear all other devices except this one from our device list and republish the list.
388     *
389     * @throws InterruptedException
390     * @throws SmackException
391     * @throws XMPPException.XMPPErrorException
392     * @throws CorruptedOmemoKeyException
393     */
394    public void purgeDevices() throws SmackException, InterruptedException, XMPPException.XMPPErrorException, CorruptedOmemoKeyException {
395        getOmemoService().publishDeviceIdIfNeeded(this,true);
396        getOmemoService().publishBundle(this);
397    }
398
399    /**
400     * Generate fresh identity keys and bundle and publish it to the server.
401     * @throws SmackException
402     * @throws InterruptedException
403     * @throws XMPPException.XMPPErrorException
404     * @throws CorruptedOmemoKeyException
405     */
406    public void regenerate() throws SmackException, InterruptedException, XMPPException.XMPPErrorException, CorruptedOmemoKeyException {
407        // create a new identity and publish new keys to the server
408        getOmemoService().regenerate(this, null);
409        getOmemoService().publishDeviceIdIfNeeded(this,false);
410        getOmemoService().publishBundle(this);
411    }
412
413    /**
414     * Send a ratchet update message. This can be used to advance the ratchet of a session in order to maintain forward
415     * secrecy.
416     *
417     * @param recipient recipient
418     * @throws UndecidedOmemoIdentityException      When the trust of session with the recipient is not decided yet
419     * @throws CorruptedOmemoKeyException           When the used identityKeys are corrupted
420     * @throws CryptoFailedException                When something fails with the crypto
421     * @throws CannotEstablishOmemoSessionException When we can't establish a session with the recipient
422     */
423    public void sendRatchetUpdateMessage(OmemoDevice recipient)
424            throws CorruptedOmemoKeyException, UndecidedOmemoIdentityException, CryptoFailedException,
425            CannotEstablishOmemoSessionException {
426        getOmemoService().sendOmemoRatchetUpdateMessage(this, recipient, false);
427    }
428
429    /**
430     * Create a new KeyTransportElement. This message will contain the AES-Key and IV that can be used eg. for encrypted
431     * Jingle file transfer.
432     *
433     * @param aesKey    AES key to transport
434     * @param iv        Initialization vector
435     * @param to        list of recipient devices
436     * @return          KeyTransportMessage
437     * @throws UndecidedOmemoIdentityException      When the trust of session with the recipient is not decided yet
438     * @throws CorruptedOmemoKeyException           When the used identityKeys are corrupted
439     * @throws CryptoFailedException                When something fails with the crypto
440     * @throws CannotEstablishOmemoSessionException When we can't establish a session with the recipient
441     */
442    public OmemoVAxolotlElement createKeyTransportElement(byte[] aesKey, byte[] iv, OmemoDevice ... to)
443            throws UndecidedOmemoIdentityException, CorruptedOmemoKeyException, CryptoFailedException,
444            CannotEstablishOmemoSessionException {
445        return getOmemoService().prepareOmemoKeyTransportElement(this, aesKey, iv, to);
446    }
447
448    /**
449     * Create a new Message from a encrypted OmemoMessageElement.
450     * Add ourselves as the sender and the encrypted element.
451     * Also tell the server to store the message despite a possible missing body.
452     * The body will be set to a hint message that we are using OMEMO.
453     *
454     * @param encrypted OmemoMessageElement
455     * @return Message containing the OMEMO element and some additional information
456     */
457    Message finishMessage(OmemoVAxolotlElement encrypted) {
458        if (encrypted == null) {
459            return null;
460        }
461
462        Message chatMessage = new Message();
463        chatMessage.setFrom(connection().getUser().asBareJid());
464        chatMessage.addExtension(encrypted);
465
466        if (OmemoConfiguration.getAddOmemoHintBody()) {
467            chatMessage.setBody(BODY_OMEMO_HINT);
468        }
469
470        if (OmemoConfiguration.getAddMAMStorageProcessingHint()) {
471            StoreHint.set(chatMessage);
472        }
473
474        if (OmemoConfiguration.getAddEmeEncryptionHint()) {
475            chatMessage.addExtension(new ExplicitMessageEncryptionElement(
476                    ExplicitMessageEncryptionElement.ExplicitMessageEncryptionProtocol.omemoVAxolotl));
477        }
478
479        return chatMessage;
480    }
481
482    /**
483     * Returns true, if the contact has any active devices published in a deviceList.
484     *
485     * @param contact contact
486     * @return true if contact has at least one OMEMO capable device.
487     * @throws SmackException.NotConnectedException
488     * @throws InterruptedException
489     * @throws SmackException.NoResponseException
490     */
491    public boolean contactSupportsOmemo(BareJid contact) throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
492        getOmemoService().refreshDeviceList(this, contact);
493        return !getOmemoService().getOmemoStoreBackend().loadCachedDeviceList(this, contact)
494                .getActiveDevices().isEmpty();
495    }
496
497    /**
498     * Returns true, if the MUC with the EntityBareJid multiUserChat is non-anonymous and members only (prerequisite
499     * for OMEMO encryption in MUC).
500     *
501     * @param multiUserChat EntityBareJid of the MUC
502     * @return true if chat supports OMEMO
503     * @throws XMPPException.XMPPErrorException     if
504     * @throws SmackException.NotConnectedException something
505     * @throws InterruptedException                 goes
506     * @throws SmackException.NoResponseException   wrong
507     */
508    public boolean multiUserChatSupportsOmemo(EntityBareJid multiUserChat) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
509        RoomInfo roomInfo = MultiUserChatManager.getInstanceFor(connection()).getRoomInfo(multiUserChat);
510        return roomInfo.isNonanonymous() && roomInfo.isMembersOnly();
511    }
512
513    /**
514     * Returns true, if the Server supports PEP.
515     *
516     * @param connection XMPPConnection
517     * @param server domainBareJid of the server to test
518     * @return true if server supports pep
519     * @throws XMPPException.XMPPErrorException
520     * @throws SmackException.NotConnectedException
521     * @throws InterruptedException
522     * @throws SmackException.NoResponseException
523     */
524    public static boolean serverSupportsOmemo(XMPPConnection connection, DomainBareJid server) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
525        return ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(server).containsFeature(PubSub.NAMESPACE);
526    }
527
528    /**
529     * Return the fingerprint of our identity key.
530     *
531     * @return fingerprint
532     */
533    public OmemoFingerprint getOurFingerprint() {
534        return getOmemoService().getOmemoStoreBackend().getFingerprint(this);
535    }
536
537    public OmemoFingerprint getFingerprint(OmemoDevice device) throws CannotEstablishOmemoSessionException {
538        if (device.equals(getOwnDevice())) {
539            return getOurFingerprint();
540        }
541
542        return getOmemoService().getOmemoStoreBackend().getFingerprint(this, device);
543    }
544
545    /**
546     * Return all fingerprints of active devices of a contact.
547     * @param contact contact
548     * @return HashMap of deviceIds and corresponding fingerprints.
549     */
550    public HashMap<OmemoDevice, OmemoFingerprint> getActiveFingerprints(BareJid contact) {
551        HashMap<OmemoDevice, OmemoFingerprint> fingerprints = new HashMap<>();
552        CachedDeviceList deviceList = getOmemoService().getOmemoStoreBackend().loadCachedDeviceList(this, contact);
553        for (int id : deviceList.getActiveDevices()) {
554            OmemoDevice device = new OmemoDevice(contact, id);
555            OmemoFingerprint fingerprint = null;
556            try {
557                fingerprint = getFingerprint(device);
558            } catch (CannotEstablishOmemoSessionException e) {
559                LOGGER.log(Level.WARNING, "Could not build session with device " + id
560                        + " of user " + contact + ": " + e.getMessage());
561            }
562
563            if (fingerprint != null) {
564                fingerprints.put(device, fingerprint);
565            }
566        }
567        return fingerprints;
568    }
569
570    public void addOmemoMessageListener(OmemoMessageListener listener) {
571        omemoMessageListeners.add(listener);
572    }
573
574    public void removeOmemoMessageListener(OmemoMessageListener listener) {
575        omemoMessageListeners.remove(listener);
576    }
577
578    public void addOmemoMucMessageListener(OmemoMucMessageListener listener) {
579        omemoMucMessageListeners.add(listener);
580    }
581
582    public void removeOmemoMucMessageListener(OmemoMucMessageListener listener) {
583        omemoMucMessageListeners.remove(listener);
584    }
585
586    /**
587     * Build OMEMO sessions with devices of contact.
588     *
589     * @param contact contact we want to build session with.
590     * @throws InterruptedException
591     * @throws CannotEstablishOmemoSessionException
592     * @throws SmackException.NotConnectedException
593     * @throws SmackException.NoResponseException
594     */
595    public void buildSessionsWith(BareJid contact) throws InterruptedException, CannotEstablishOmemoSessionException, SmackException.NotConnectedException, SmackException.NoResponseException {
596        getOmemoService().buildOrCreateOmemoSessionsFromBundles(this, contact);
597    }
598
599    /**
600     * Request a deviceList update from contact contact.
601     *
602     * @param contact contact we want to obtain the deviceList from.
603     * @throws SmackException.NotConnectedException
604     * @throws InterruptedException
605     * @throws SmackException.NoResponseException
606     */
607    public void requestDeviceListUpdateFor(BareJid contact) throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
608        getOmemoService().refreshDeviceList(this, contact);
609    }
610
611    /**
612     * Rotate the signedPreKey published in our OmemoBundle. This should be done every now and then (7-14 days).
613     * The old signedPreKey should be kept for some more time (a month or so) to enable decryption of messages
614     * that have been sent since the key was changed.
615     *
616     * @throws CorruptedOmemoKeyException When the IdentityKeyPair is damaged.
617     * @throws InterruptedException XMPP error
618     * @throws XMPPException.XMPPErrorException XMPP error
619     * @throws SmackException.NotConnectedException XMPP error
620     * @throws SmackException.NoResponseException XMPP error
621     * @throws PubSubException.NotALeafNodeException if the bundle node on the server is a CollectionNode
622     */
623    public void rotateSignedPreKey() throws CorruptedOmemoKeyException, InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException, PubSubException.NotALeafNodeException {
624        // generate key
625        getOmemoService().getOmemoStoreBackend().changeSignedPreKey(this);
626        // publish
627        getOmemoService().publishDeviceIdIfNeeded(this, false);
628        getOmemoService().publishBundle(this);
629    }
630
631    /**
632     * Return true, if the given Stanza contains an OMEMO element 'encrypted'.
633     * @param stanza stanza
634     * @return true if stanza has extension 'encrypted'
635     */
636    public static boolean stanzaContainsOmemoElement(Stanza stanza) {
637        return stanza.hasExtension(OmemoElement.ENCRYPTED, OMEMO_NAMESPACE_V_AXOLOTL);
638    }
639
640    /**
641     * Throw an IllegalStateException if no OmemoService is set.
642     */
643    private void throwIfNoServiceSet() {
644        if (service == null) {
645            throw new IllegalStateException("No OmemoService set in OmemoManager.");
646        }
647    }
648
649    public static int randomDeviceId() {
650        int i = new Random().nextInt(Integer.MAX_VALUE);
651
652        if (i == 0) {
653            return randomDeviceId();
654        }
655
656        return Math.abs(i);
657    }
658
659    /**
660     * Return the BareJid of the user.
661     *
662     * @return bareJid
663     */
664    public BareJid getOwnJid() {
665        EntityFullJid fullJid = connection().getUser();
666        if (fullJid == null) return null;
667        return fullJid.asBareJid();
668    }
669
670    /**
671     * Return the deviceId of this OmemoManager.
672     *
673     * @return deviceId
674     */
675    public int getDeviceId() {
676        return deviceId;
677    }
678
679    /**
680     * Return the OmemoDevice of the user.
681     *
682     * @return omemoDevice
683     */
684    public OmemoDevice getOwnDevice() {
685        return new OmemoDevice(getOwnJid(), getDeviceId());
686    }
687
688    void setDeviceId(int nDeviceId) {
689        INSTANCES.get(connection()).remove(getDeviceId());
690        INSTANCES.get(connection()).put(nDeviceId, this);
691        this.deviceId = nDeviceId;
692    }
693
694    /**
695     * Notify all registered OmemoMessageListeners about a received OmemoMessage.
696     *
697     * @param decryptedBody      decrypted Body element of the message
698     * @param encryptedMessage   unmodified message as it was received
699     * @param wrappingMessage    message that wrapped the incoming message
700     * @param messageInformation information about the messages encryption (used identityKey, carbon...)
701     */
702    void notifyOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation messageInformation) {
703        for (OmemoMessageListener l : omemoMessageListeners) {
704            l.onOmemoMessageReceived(decryptedBody, encryptedMessage, wrappingMessage, messageInformation);
705        }
706    }
707
708    void notifyOmemoKeyTransportMessageReceived(CipherAndAuthTag cipherAndAuthTag, Message transportingMessage,
709                                                Message wrappingMessage, OmemoMessageInformation information) {
710        for (OmemoMessageListener l : omemoMessageListeners) {
711            l.onOmemoKeyTransportReceived(cipherAndAuthTag, transportingMessage, wrappingMessage, information);
712        }
713    }
714
715    /**
716     * Notify all registered OmemoMucMessageListeners of an incoming OmemoMessageElement in a MUC.
717     *
718     * @param muc              MultiUserChat the message was received in
719     * @param from             BareJid of the user that sent the message
720     * @param decryptedBody    decrypted body
721     * @param message          original message with encrypted content
722     * @param wrappingMessage  wrapping message (in case of carbon copy)
723     * @param omemoInformation information about the encryption of the message
724     */
725    void notifyOmemoMucMessageReceived(MultiUserChat muc, BareJid from, String decryptedBody, Message message,
726                                               Message wrappingMessage, OmemoMessageInformation omemoInformation) {
727        for (OmemoMucMessageListener l : omemoMucMessageListeners) {
728            l.onOmemoMucMessageReceived(muc, from, decryptedBody, message,
729                    wrappingMessage, omemoInformation);
730        }
731    }
732
733    void notifyOmemoMucKeyTransportMessageReceived(MultiUserChat muc, BareJid from, CipherAndAuthTag cipherAndAuthTag,
734                                                   Message transportingMessage, Message wrappingMessage,
735                                                   OmemoMessageInformation messageInformation) {
736        for (OmemoMucMessageListener l : omemoMucMessageListeners) {
737            l.onOmemoKeyTransportReceived(muc, from, cipherAndAuthTag,
738                    transportingMessage, wrappingMessage, messageInformation);
739        }
740    }
741
742    /**
743     * Remove all active stanza listeners of this manager from the connection.
744     * This is somewhat the counterpart of initialize().
745     */
746    public void shutdown() {
747        PEPManager.getInstanceFor(connection()).removePEPListener(deviceListUpdateListener);
748        connection().removeAsyncStanzaListener(omemoStanzaListener);
749        CarbonManager.getInstanceFor(connection()).removeCarbonCopyReceivedListener(omemoCarbonCopyListener);
750    }
751
752    /**
753     * Get our connection.
754     *
755     * @return the connection of this manager
756     */
757    XMPPConnection getConnection() {
758        return connection();
759    }
760
761    /**
762     * Return the OMEMO service object.
763     *
764     * @return omemoService
765     */
766    OmemoService<?,?,?,?,?,?,?,?,?> getOmemoService() {
767        throwIfNoServiceSet();
768        return service;
769    }
770
771    PEPListener deviceListUpdateListener = new PEPListener() {
772        @Override
773        public void eventReceived(EntityBareJid from, EventElement event, Message message) {
774            for (ExtensionElement items : event.getExtensions()) {
775                if (!(items instanceof ItemsExtension)) {
776                    continue;
777                }
778
779                for (ExtensionElement item : ((ItemsExtension) items).getItems()) {
780                    if (!(item instanceof PayloadItem<?>)) {
781                        continue;
782                    }
783
784                    PayloadItem<?> payloadItem = (PayloadItem<?>) item;
785
786                    if (!(payloadItem.getPayload() instanceof  OmemoDeviceListVAxolotlElement)) {
787                        continue;
788                    }
789
790                    // Device List <list>
791                    OmemoDeviceListVAxolotlElement omemoDeviceListElement = (OmemoDeviceListVAxolotlElement) payloadItem.getPayload();
792                    int ourDeviceId = getDeviceId();
793                    getOmemoService().getOmemoStoreBackend().mergeCachedDeviceList(OmemoManager.this, from, omemoDeviceListElement);
794
795                    if (from == null) {
796                        // Unknown sender, no more work to do.
797                        // TODO: This DOES happen for some reason. Figure out when...
798                        continue;
799                    }
800
801                    if (!from.equals(getOwnJid())) {
802                        // Not our deviceList, so nothing more to do
803                        continue;
804                    }
805
806                    if (omemoDeviceListElement.getDeviceIds().contains(ourDeviceId)) {
807                        // We are on the list. Nothing more to do
808                        continue;
809                    }
810
811                    // Our deviceList and we are not on it! We don't want to miss all the action!!!
812                    LOGGER.log(Level.INFO, "Our deviceId was not on the list!");
813                    Set<Integer> deviceListIds = omemoDeviceListElement.copyDeviceIds();
814                    // enroll at the deviceList
815                    deviceListIds.add(ourDeviceId);
816                    final OmemoDeviceListVAxolotlElement newOmemoDeviceListElement = new OmemoDeviceListVAxolotlElement(deviceListIds);
817
818                    // PEPListener is a synchronous listener. Avoid any deadlocks by using an async task to update the device list.
819                    Async.go(new Runnable() {
820                        @Override
821                        public void run() {
822                            try {
823                                OmemoService.publishDeviceIds(OmemoManager.this, newOmemoDeviceListElement);
824                            }
825                            catch (SmackException | InterruptedException | XMPPException.XMPPErrorException e) {
826                                // TODO: It might be dangerous NOT to retry publishing our deviceId
827                                LOGGER.log(Level.SEVERE,
828                                                "Could not publish our device list after an update without our id was received: "
829                                                                + e.getMessage());
830                            }
831                        }
832                    });
833                }
834            }
835        }
836    };
837
838
839
840    OmemoService<?,?,?,?,?,?,?,?,?>.OmemoStanzaListener getOmemoStanzaListener() {
841        if (omemoStanzaListener == null) {
842            omemoStanzaListener = getOmemoService().createStanzaListener(this);
843        }
844        return omemoStanzaListener;
845    }
846
847    OmemoService<?,?,?,?,?,?,?,?,?>.OmemoCarbonCopyListener getOmemoCarbonCopyListener() {
848        if (omemoCarbonCopyListener == null) {
849            omemoCarbonCopyListener = getOmemoService().createOmemoCarbonCopyListener(this);
850        }
851        return omemoCarbonCopyListener;
852    }
853}