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