OmemoService.java

  1. /**
  2.  *
  3.  * Copyright 2017 Paul Schaub, 2019 Florian Schmaus
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smackx.omemo;

  18. import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYLENGTH;
  19. import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYTYPE;

  20. import java.io.IOException;
  21. import java.security.InvalidAlgorithmParameterException;
  22. import java.security.InvalidKeyException;
  23. import java.security.NoSuchAlgorithmException;
  24. import java.util.ArrayList;
  25. import java.util.Collection;
  26. import java.util.Collections;
  27. import java.util.Date;
  28. import java.util.HashMap;
  29. import java.util.HashSet;
  30. import java.util.List;
  31. import java.util.Random;
  32. import java.util.Set;
  33. import java.util.logging.Level;
  34. import java.util.logging.Logger;

  35. import javax.crypto.BadPaddingException;
  36. import javax.crypto.IllegalBlockSizeException;
  37. import javax.crypto.NoSuchPaddingException;

  38. import org.jivesoftware.smack.SmackException;
  39. import org.jivesoftware.smack.XMPPConnection;
  40. import org.jivesoftware.smack.XMPPException;
  41. import org.jivesoftware.smack.packet.Message;
  42. import org.jivesoftware.smack.packet.Stanza;
  43. import org.jivesoftware.smack.packet.StanzaError;

  44. import org.jivesoftware.smackx.carbons.packet.CarbonExtension;
  45. import org.jivesoftware.smackx.mam.MamManager;
  46. import org.jivesoftware.smackx.muc.MultiUserChat;
  47. import org.jivesoftware.smackx.muc.MultiUserChatManager;
  48. import org.jivesoftware.smackx.muc.Occupant;
  49. import org.jivesoftware.smackx.omemo.element.OmemoBundleElement;
  50. import org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement;
  51. import org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement_VAxolotl;
  52. import org.jivesoftware.smackx.omemo.element.OmemoElement;
  53. import org.jivesoftware.smackx.omemo.element.OmemoElement_VAxolotl;
  54. import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException;
  55. import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
  56. import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
  57. import org.jivesoftware.smackx.omemo.exceptions.NoIdentityKeyException;
  58. import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException;
  59. import org.jivesoftware.smackx.omemo.exceptions.ReadOnlyDeviceException;
  60. import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException;
  61. import org.jivesoftware.smackx.omemo.exceptions.UntrustedOmemoIdentityException;
  62. import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
  63. import org.jivesoftware.smackx.omemo.internal.OmemoCachedDeviceList;
  64. import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
  65. import org.jivesoftware.smackx.omemo.internal.listener.OmemoCarbonCopyStanzaReceivedListener;
  66. import org.jivesoftware.smackx.omemo.internal.listener.OmemoMessageStanzaReceivedListener;
  67. import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint;
  68. import org.jivesoftware.smackx.omemo.trust.OmemoTrustCallback;
  69. import org.jivesoftware.smackx.omemo.trust.TrustState;
  70. import org.jivesoftware.smackx.omemo.util.MessageOrOmemoMessage;
  71. import org.jivesoftware.smackx.omemo.util.OmemoConstants;
  72. import org.jivesoftware.smackx.omemo.util.OmemoMessageBuilder;
  73. import org.jivesoftware.smackx.pep.PepManager;
  74. import org.jivesoftware.smackx.pubsub.LeafNode;
  75. import org.jivesoftware.smackx.pubsub.PayloadItem;
  76. import org.jivesoftware.smackx.pubsub.PubSubException;
  77. import org.jivesoftware.smackx.pubsub.PubSubException.NotALeafNodeException;
  78. import org.jivesoftware.smackx.pubsub.PubSubManager;

  79. import org.jxmpp.jid.BareJid;
  80. import org.jxmpp.jid.EntityBareJid;
  81. import org.jxmpp.jid.Jid;

  82. /**
  83.  * This class contains OMEMO related logic and registers listeners etc.
  84.  *
  85.  * @param <T_IdKeyPair> IdentityKeyPair class
  86.  * @param <T_IdKey>     IdentityKey class
  87.  * @param <T_PreKey>    PreKey class
  88.  * @param <T_SigPreKey> SignedPreKey class
  89.  * @param <T_Sess>      Session class
  90.  * @param <T_Addr>      Address class
  91.  * @param <T_ECPub>     Elliptic Curve PublicKey class
  92.  * @param <T_Bundle>    Bundle class
  93.  * @param <T_Ciph>      Cipher class
  94.  *
  95.  * @author Paul Schaub
  96.  */
  97. public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
  98.         implements OmemoCarbonCopyStanzaReceivedListener, OmemoMessageStanzaReceivedListener {

  99.     protected static final Logger LOGGER = Logger.getLogger(OmemoService.class.getName());

  100.     private static final long MILLIS_PER_HOUR = 1000L * 60 * 60;

  101.     private static OmemoService<?, ?, ?, ?, ?, ?, ?, ?, ?> INSTANCE;

  102.     private OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore;
  103.     private final HashMap<OmemoManager, OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>> omemoRatchets = new HashMap<>();

  104.     protected OmemoService() {

  105.     }

  106.     /**
  107.      * Return the singleton instance of this class. When no instance is set, throw an IllegalStateException instead.
  108.      *
  109.      * @return instance.
  110.      */
  111.     public static OmemoService<?, ?, ?, ?, ?, ?, ?, ?, ?> getInstance() {
  112.         if (INSTANCE == null) {
  113.             throw new IllegalStateException("No OmemoService registered");
  114.         }
  115.         return INSTANCE;
  116.     }

  117.     /**
  118.      * Set singleton instance. Throws an IllegalStateException, if there is already a service set as instance.
  119.      *
  120.      * @param omemoService instance
  121.      */
  122.     protected static void setInstance(OmemoService<?, ?, ?, ?, ?, ?, ?, ?, ?> omemoService) {
  123.         if (INSTANCE != null) {
  124.             throw new IllegalStateException("An OmemoService is already registered");
  125.         }
  126.         INSTANCE = omemoService;
  127.     }

  128.     /**
  129.      * Returns true, if an instance of the service singleton is set. Otherwise return false.
  130.      *
  131.      * @return true, if instance is not null.
  132.      */
  133.     public static boolean isServiceRegistered() {
  134.         return INSTANCE != null;
  135.     }

  136.     /**
  137.      * Return the used omemoStore backend.
  138.      * If there is no store backend set yet, set the default one (typically a file-based one).
  139.      *
  140.      * @return omemoStore backend
  141.      */
  142.     public OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
  143.     getOmemoStoreBackend() {
  144.         if (omemoStore == null) {
  145.             omemoStore = createDefaultOmemoStoreBackend();
  146.         }
  147.         return omemoStore;
  148.     }

  149.     /**
  150.      * Set an omemoStore as backend. Throws an IllegalStateException, if there is already a backend set.
  151.      *
  152.      * @param omemoStore store.
  153.      */
  154.     @SuppressWarnings("unused")
  155.     public void setOmemoStoreBackend(
  156.             OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore) {
  157.         if (this.omemoStore != null) {
  158.             throw new IllegalStateException("An OmemoStore backend has already been set.");
  159.         }
  160.         this.omemoStore = omemoStore;
  161.     }

  162.     /**
  163.      * Create a default OmemoStore object.
  164.      *
  165.      * @return default omemoStore.
  166.      */
  167.     protected abstract OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
  168.     createDefaultOmemoStoreBackend();

  169.     /**
  170.      * Return a new instance of the OMEMO ratchet.
  171.      * The ratchet is internally used to encrypt/decrypt message keys.
  172.      *
  173.      * @param manager OmemoManager
  174.      * @param store OmemoStore
  175.      * @return instance of the OmemoRatchet
  176.      */
  177.     protected abstract OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
  178.     instantiateOmemoRatchet(OmemoManager manager,
  179.                             OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> store);

  180.     /**
  181.      * Return the deposited instance of the OmemoRatchet for the given manager.
  182.      * If there is none yet, create a new one, deposit it and return it.
  183.      *
  184.      * @param manager OmemoManager we want to have the ratchet for.
  185.      * @return OmemoRatchet instance
  186.      */
  187.     protected OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
  188.     getOmemoRatchet(OmemoManager manager) {
  189.         OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
  190.                 omemoRatchet = omemoRatchets.get(manager);
  191.         if (omemoRatchet == null) {
  192.             omemoRatchet = instantiateOmemoRatchet(manager, omemoStore);
  193.             omemoRatchets.put(manager, omemoRatchet);
  194.         }
  195.         return omemoRatchet;
  196.     }

  197.     /**
  198.      * Instantiate and deposit a Ratchet for the given OmemoManager.
  199.      *
  200.      * @param manager manager.
  201.      */
  202.     void registerRatchetForManager(OmemoManager manager) {
  203.         omemoRatchets.put(manager, instantiateOmemoRatchet(manager, getOmemoStoreBackend()));
  204.     }

  205.     /**
  206.      * Initialize OMEMO functionality for the given {@link OmemoManager}.
  207.      *
  208.      * @param managerGuard OmemoManager we'd like to initialize.
  209.      *
  210.      * @throws InterruptedException if the calling thread was interrupted.
  211.      * @throws CorruptedOmemoKeyException if the OMEMO key is corrupted.
  212.      * @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
  213.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  214.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  215.      * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
  216.      * @throws IOException if an I/O error occurred.
  217.      */
  218.     void init(OmemoManager.LoggedInOmemoManager managerGuard)
  219.             throws InterruptedException, CorruptedOmemoKeyException, XMPPException.XMPPErrorException,
  220.             SmackException.NotConnectedException, SmackException.NoResponseException,
  221.             PubSubException.NotALeafNodeException, IOException {

  222.         OmemoManager manager = managerGuard.get();
  223.         OmemoDevice userDevice = manager.getOwnDevice();

  224.         // Create new keys if necessary and publish to the server.
  225.         getOmemoStoreBackend().replenishKeys(userDevice);

  226.         // Rotate signed preKey if necessary.
  227.         if (shouldRotateSignedPreKey(userDevice)) {
  228.             getOmemoStoreBackend().changeSignedPreKey(userDevice);
  229.         }

  230.         // Pack and publish bundle
  231.         OmemoBundleElement bundle = getOmemoStoreBackend().packOmemoBundle(userDevice);
  232.         publishBundle(manager.getConnection(), userDevice, bundle);

  233.         // Fetch device list and republish deviceId if necessary
  234.         refreshAndRepublishDeviceList(manager.getConnection(), userDevice);
  235.     }

  236.     /**
  237.      * Create an empty OMEMO message, which is used to forward the ratchet of the recipient.
  238.      * This message type is typically used to create stable sessions.
  239.      * Note that trust decisions are ignored for the creation of this message.
  240.      *
  241.      * @param managerGuard Logged in OmemoManager
  242.      * @param contactsDevice OmemoDevice of the contact
  243.      * @return ratchet update message
  244.      *
  245.      * @throws NoSuchAlgorithmException if AES algorithms are not supported on this system.
  246.      * @throws InterruptedException if the calling thread was interrupted.
  247.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  248.      * @throws CorruptedOmemoKeyException if our IdentityKeyPair is corrupted.
  249.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  250.      * @throws CannotEstablishOmemoSessionException if session negotiation fails.
  251.      * @throws IOException if an I/O error occurred.
  252.      */
  253.     OmemoElement createRatchetUpdateElement(OmemoManager.LoggedInOmemoManager managerGuard,
  254.                                             OmemoDevice contactsDevice)
  255.             throws InterruptedException, SmackException.NoResponseException, CorruptedOmemoKeyException,
  256.             SmackException.NotConnectedException, CannotEstablishOmemoSessionException, NoSuchAlgorithmException,
  257.             CryptoFailedException, IOException {

  258.         OmemoManager manager = managerGuard.get();
  259.         OmemoDevice userDevice = manager.getOwnDevice();

  260.         if (contactsDevice.equals(userDevice)) {
  261.             throw new IllegalArgumentException("\"Thou shall not update thy own ratchet!\" - William Shakespeare");
  262.         }

  263.         // Establish session if necessary
  264.         if (!hasSession(userDevice, contactsDevice)) {
  265.             buildFreshSessionWithDevice(manager.getConnection(), userDevice, contactsDevice);
  266.         }

  267.         // Generate fresh AES key and IV
  268.         byte[] messageKey = OmemoMessageBuilder.generateKey(KEYTYPE, KEYLENGTH);
  269.         byte[] iv = OmemoMessageBuilder.generateIv();

  270.         // Create message builder
  271.         OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> builder;
  272.         try {
  273.             builder = new OmemoMessageBuilder<>(userDevice, gullibleTrustCallback, getOmemoRatchet(manager),
  274.                     messageKey, iv, null);
  275.         } catch (InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException e) {
  276.             throw new CryptoFailedException(e);
  277.         }

  278.         // Add recipient
  279.         try {
  280.             builder.addRecipient(contactsDevice);
  281.         } catch (UndecidedOmemoIdentityException | UntrustedOmemoIdentityException e) {
  282.             throw new AssertionError("Gullible Trust Callback reported undecided or untrusted device, " +
  283.                     "even though it MUST NOT do that.");
  284.         } catch (NoIdentityKeyException e) {
  285.             throw new AssertionError("We MUST have an identityKey for " + contactsDevice + " since we built a session." + e);
  286.         }

  287.         // Note: We don't need to update our message counter for a ratchet update message.

  288.         return builder.finish();
  289.     }

  290.     /**
  291.      * Encrypt a message with a messageKey and an IV and create an OmemoMessage from it.
  292.      *
  293.      * @param managerGuard authenticated OmemoManager
  294.      * @param contactsDevices set of recipient OmemoDevices
  295.      * @param messageKey AES key to encrypt the message
  296.      * @param iv iv to be used with the messageKey
  297.      * @return OmemoMessage object which contains the OmemoElement and some information.
  298.      *
  299.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  300.      * @throws InterruptedException if the calling thread was interrupted.
  301.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  302.      * @throws UndecidedOmemoIdentityException if the list of recipient devices contains undecided devices
  303.      * @throws CryptoFailedException if we are lacking some crypto primitives
  304.      * @throws IOException if an I/O error occurred.
  305.      */
  306.     private OmemoMessage.Sent encrypt(OmemoManager.LoggedInOmemoManager managerGuard,
  307.                                       Set<OmemoDevice> contactsDevices,
  308.                                       byte[] messageKey,
  309.                                       byte[] iv,
  310.                                       String message)
  311.             throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException,
  312.             UndecidedOmemoIdentityException, CryptoFailedException, IOException {

  313.         OmemoManager manager = managerGuard.get();
  314.         OmemoDevice userDevice = manager.getOwnDevice();

  315.         // Do not encrypt for our own device.
  316.         removeOurDevice(userDevice, contactsDevices);

  317.         buildMissingSessionsWithDevices(manager.getConnection(), userDevice, contactsDevices);

  318.         Set<OmemoDevice> undecidedDevices = getUndecidedDevices(userDevice, manager.getTrustCallback(), contactsDevices);
  319.         if (!undecidedDevices.isEmpty()) {
  320.             throw new UndecidedOmemoIdentityException(undecidedDevices);
  321.         }

  322.         // Keep track of skipped devices
  323.         HashMap<OmemoDevice, Throwable> skippedRecipients = new HashMap<>();

  324.         OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> builder;
  325.         try {
  326.             builder = new OmemoMessageBuilder<>(
  327.                     userDevice, manager.getTrustCallback(), getOmemoRatchet(managerGuard.get()), messageKey, iv, message);
  328.         } catch (BadPaddingException | IllegalBlockSizeException |
  329.                 NoSuchPaddingException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException e) {
  330.             throw new CryptoFailedException(e);
  331.         }

  332.         for (OmemoDevice contactsDevice : contactsDevices) {
  333.             // Build missing sessions
  334.             if (!hasSession(userDevice, contactsDevice)) {
  335.                 try {
  336.                     buildFreshSessionWithDevice(manager.getConnection(), userDevice, contactsDevice);
  337.                 } catch (CorruptedOmemoKeyException | CannotEstablishOmemoSessionException e) {
  338.                     LOGGER.log(Level.WARNING, "Could not build session with " + contactsDevice + ".", e);
  339.                     skippedRecipients.put(contactsDevice, e);
  340.                     continue;
  341.                 }
  342.             }

  343.             int messageCounter = omemoStore.loadOmemoMessageCounter(userDevice, contactsDevice);

  344.             // Ignore read-only devices
  345.             if (OmemoConfiguration.getIgnoreReadOnlyDevices()) {

  346.                 boolean readOnly = messageCounter >= OmemoConfiguration.getMaxReadOnlyMessageCount();

  347.                 if (readOnly) {
  348.                     LOGGER.log(Level.FINE, "Device " + contactsDevice + " seems to be read-only (We sent "
  349.                             + messageCounter + " messages without getting a reply back (max allowed is " +
  350.                             OmemoConfiguration.getMaxReadOnlyMessageCount() + "). Ignoring the device.");
  351.                     skippedRecipients.put(contactsDevice, new ReadOnlyDeviceException(contactsDevice));

  352.                     // Skip this device and handle next device
  353.                     continue;
  354.                 }
  355.             }

  356.             // Add recipients
  357.             try {
  358.                 builder.addRecipient(contactsDevice);
  359.             }
  360.             catch (NoIdentityKeyException | CorruptedOmemoKeyException e) {
  361.                 LOGGER.log(Level.WARNING, "Encryption failed for device " + contactsDevice + ".", e);
  362.                 skippedRecipients.put(contactsDevice, e);
  363.             }
  364.             catch (UndecidedOmemoIdentityException e) {
  365.                 throw new AssertionError("Recipients device seems to be undecided, even though we should have thrown" +
  366.                         " an exception earlier in that case. " + e);
  367.             }
  368.             catch (UntrustedOmemoIdentityException e) {
  369.                 LOGGER.log(Level.WARNING, "Device " + contactsDevice + " is untrusted. Message is not encrypted for it.");
  370.                 skippedRecipients.put(contactsDevice, e);
  371.             }

  372.             // Increment the message counter of the device
  373.             omemoStore.storeOmemoMessageCounter(userDevice, contactsDevice,
  374.                     messageCounter + 1);
  375.         }

  376.         OmemoElement element = builder.finish();

  377.         return new OmemoMessage.Sent(element, messageKey, iv, contactsDevices, skippedRecipients);
  378.     }

  379.     /**
  380.      * Decrypt an OMEMO message.
  381.      *
  382.      * @param managerGuard authenticated OmemoManager.
  383.      * @param senderJid BareJid of the sender.
  384.      * @param omemoElement omemoElement.
  385.      * @return decrypted OmemoMessage object.
  386.      *
  387.      * @throws CorruptedOmemoKeyException if the identityKey of the sender is damaged.
  388.      * @throws CryptoFailedException if decryption fails.
  389.      * @throws NoRawSessionException if we have no session with the device and it sent a normal (non-preKey) message.
  390.      * @throws IOException if an I/O error occurred.
  391.      */
  392.     OmemoMessage.Received decryptMessage(OmemoManager.LoggedInOmemoManager managerGuard,
  393.                                          BareJid senderJid,
  394.                                          OmemoElement omemoElement)
  395.             throws CorruptedOmemoKeyException, CryptoFailedException, NoRawSessionException, IOException {

  396.         OmemoManager manager = managerGuard.get();
  397.         int senderId = omemoElement.getHeader().getSid();
  398.         OmemoDevice senderDevice = new OmemoDevice(senderJid, senderId);

  399.         CipherAndAuthTag cipherAndAuthTag = getOmemoRatchet(manager)
  400.                 .retrieveMessageKeyAndAuthTag(senderDevice, omemoElement);

  401.         // Retrieve senders fingerprint.
  402.         OmemoFingerprint senderFingerprint;
  403.         try {
  404.             senderFingerprint = getOmemoStoreBackend().getFingerprint(manager.getOwnDevice(), senderDevice);
  405.         } catch (NoIdentityKeyException e) {
  406.             throw new AssertionError("Cannot retrieve OmemoFingerprint of sender although decryption was successful: " + e);
  407.         }

  408.         // Reset the message counter.
  409.         omemoStore.storeOmemoMessageCounter(manager.getOwnDevice(), senderDevice, 0);

  410.         if (omemoElement.isMessageElement()) {
  411.             // Use symmetric message key to decrypt message payload.
  412.             String plaintext = OmemoRatchet.decryptMessageElement(omemoElement, cipherAndAuthTag);

  413.             return new OmemoMessage.Received(omemoElement, cipherAndAuthTag.getKey(), cipherAndAuthTag.getIv(),
  414.                     plaintext, senderFingerprint, senderDevice, cipherAndAuthTag.wasPreKeyEncrypted());

  415.         } else {
  416.             // KeyTransportMessages don't require decryption of the payload.
  417.             return new OmemoMessage.Received(omemoElement, cipherAndAuthTag.getKey(), cipherAndAuthTag.getIv(),
  418.                     null, senderFingerprint, senderDevice, cipherAndAuthTag.wasPreKeyEncrypted());
  419.         }
  420.     }

  421.     /**
  422.      * Create an OMEMO KeyTransportElement.
  423.      *
  424.      * @see <a href="https://xmpp.org/extensions/xep-0384.html#usecases-keysend">XEP-0384: Sending a key</a>.
  425.      *
  426.      * @param managerGuard Initialized OmemoManager.
  427.      * @param contactsDevices set of recipient devices.
  428.      * @param key AES-Key to be transported.
  429.      * @param iv initialization vector to be used with the key.
  430.      *
  431.      * @return a new key transport element
  432.      *
  433.      * @throws InterruptedException if the calling thread was interrupted.
  434.      * @throws UndecidedOmemoIdentityException if the list of recipients contains an undecided device
  435.      * @throws CryptoFailedException if we are lacking some cryptographic algorithms
  436.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  437.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  438.      * @throws IOException if an I/O error occurred.
  439.      */
  440.     OmemoMessage.Sent createKeyTransportElement(OmemoManager.LoggedInOmemoManager managerGuard,
  441.                                                 Set<OmemoDevice> contactsDevices,
  442.                                                 byte[] key,
  443.                                                 byte[] iv)
  444.             throws InterruptedException, UndecidedOmemoIdentityException, CryptoFailedException,
  445.             SmackException.NotConnectedException, SmackException.NoResponseException, IOException {
  446.         return encrypt(managerGuard, contactsDevices, key, iv, null);
  447.     }

  448.     /**
  449.      * Create an OmemoMessage.
  450.      *
  451.      * @param managerGuard initialized OmemoManager
  452.      * @param contactsDevices set of recipient devices
  453.      * @param message message we want to send
  454.      * @return encrypted OmemoMessage
  455.      *
  456.      * @throws InterruptedException if the calling thread was interrupted.
  457.      * @throws UndecidedOmemoIdentityException if the list of recipient devices contains an undecided device.
  458.      * @throws CryptoFailedException if we are lacking some cryptographic algorithms
  459.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  460.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  461.      * @throws IOException if an I/O error occurred.
  462.      */
  463.     OmemoMessage.Sent createOmemoMessage(OmemoManager.LoggedInOmemoManager managerGuard,
  464.                                          Set<OmemoDevice> contactsDevices,
  465.                                          String message)
  466.             throws InterruptedException, UndecidedOmemoIdentityException, CryptoFailedException,
  467.             SmackException.NotConnectedException, SmackException.NoResponseException, IOException {

  468.         byte[] key, iv;
  469.         iv = OmemoMessageBuilder.generateIv();

  470.         try {
  471.             key = OmemoMessageBuilder.generateKey(KEYTYPE, KEYLENGTH);
  472.         } catch (NoSuchAlgorithmException e) {
  473.             throw new CryptoFailedException(e);
  474.         }

  475.         return encrypt(managerGuard, contactsDevices, key, iv, message);
  476.     }

  477.     /**
  478.      * Retrieve a users OMEMO bundle.
  479.      *
  480.      * @param connection authenticated XMPP connection.
  481.      * @param contactsDevice device of which we want to retrieve the bundle.
  482.      * @return OmemoBundle of the device or null, if it doesn't exist.
  483.      *
  484.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  485.      * @throws InterruptedException if the calling thread was interrupted.
  486.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  487.      * @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
  488.      * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
  489.      * @throws PubSubException.NotAPubSubNodeException if a involved node is not a PubSub node.
  490.      */
  491.     private static OmemoBundleElement fetchBundle(XMPPConnection connection,
  492.                                                   OmemoDevice contactsDevice)
  493.             throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException,
  494.             XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException,
  495.             PubSubException.NotAPubSubNodeException {

  496.         PubSubManager pm = PubSubManager.getInstanceFor(connection, contactsDevice.getJid());
  497.         LeafNode node = pm.getLeafNode(contactsDevice.getBundleNodeName());

  498.         if (node == null) {
  499.             return null;
  500.         }

  501.         List<PayloadItem<OmemoBundleElement>> bundleItems = node.getItems();
  502.         if (bundleItems.isEmpty()) {
  503.             return null;
  504.         }

  505.         return bundleItems.get(bundleItems.size() - 1).getPayload();
  506.     }

  507.     /**
  508.      * Publish the given OMEMO bundle to the server using PubSub.
  509.      *
  510.      * @param connection our connection.
  511.      * @param userDevice our device
  512.      * @param bundle the bundle we want to publish
  513.      *
  514.      * @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
  515.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  516.      * @throws InterruptedException if the calling thread was interrupted.
  517.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  518.      * @throws NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
  519.      */
  520.     static void publishBundle(XMPPConnection connection, OmemoDevice userDevice, OmemoBundleElement bundle)
  521.             throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
  522.             SmackException.NoResponseException, NotALeafNodeException {
  523.         PepManager pm = PepManager.getInstanceFor(connection);
  524.         pm.publish(userDevice.getBundleNodeName(), new PayloadItem<>(bundle));
  525.     }

  526.     /**
  527.      * Retrieve the OMEMO device list of a contact.
  528.      *
  529.      * @param connection authenticated XMPP connection.
  530.      * @param contact BareJid of the contact of which we want to retrieve the device list from.
  531.      * @return device list
  532.      *
  533.      * @throws InterruptedException if the calling thread was interrupted.
  534.      * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
  535.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  536.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  537.      * @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
  538.      * @throws PubSubException.NotAPubSubNodeException if a involved node is not a PubSub node.
  539.      */
  540.     private static OmemoDeviceListElement fetchDeviceList(XMPPConnection connection, BareJid contact)
  541.             throws InterruptedException, PubSubException.NotALeafNodeException, SmackException.NoResponseException,
  542.             SmackException.NotConnectedException, XMPPException.XMPPErrorException,
  543.             PubSubException.NotAPubSubNodeException {

  544.         PubSubManager pm = PubSubManager.getInstanceFor(connection, contact);
  545.         String nodeName = OmemoConstants.PEP_NODE_DEVICE_LIST;
  546.         LeafNode node = pm.getLeafNode(nodeName);

  547.         if (node == null) {
  548.             return null;
  549.         }

  550.         List<PayloadItem<OmemoDeviceListElement>> items = node.getItems();
  551.         if (items.isEmpty()) {
  552.             return null;
  553.         }

  554.         return items.get(items.size() - 1).getPayload();
  555.     }

  556.     /**
  557.      * Publish the given device list to the server.
  558.      *
  559.      * @param connection authenticated XMPP connection.
  560.      * @param deviceList users deviceList.
  561.      *
  562.      * @throws InterruptedException if the calling thread was interrupted.
  563.      * @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
  564.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  565.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  566.      * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
  567.      */
  568.     static void publishDeviceList(XMPPConnection connection, OmemoDeviceListElement deviceList)
  569.             throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException,
  570.             SmackException.NoResponseException, NotALeafNodeException {
  571.         PepManager pm = PepManager.getInstanceFor(connection);
  572.         pm.publish(OmemoConstants.PEP_NODE_DEVICE_LIST, new PayloadItem<>(deviceList));
  573.     }

  574.     /**
  575.      * Refresh our own device list and publish it to the server.
  576.      *
  577.      * @param connection XMPPConnection
  578.      * @param userDevice our OMEMO device
  579.      *
  580.      * @throws InterruptedException if the calling thread was interrupted.
  581.      * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
  582.      * @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
  583.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  584.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  585.      * @throws IOException if an I/O error occurred.
  586.      */
  587.     private void refreshAndRepublishDeviceList(XMPPConnection connection, OmemoDevice userDevice)
  588.             throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
  589.             SmackException.NotConnectedException, SmackException.NoResponseException, IOException {

  590.         // refreshOmemoDeviceList;
  591.         OmemoDeviceListElement publishedList;

  592.         try {
  593.             publishedList = fetchDeviceList(connection, userDevice.getJid());
  594.         } catch (PubSubException.NotAPubSubNodeException e) {
  595.             // Node is not a PubSub node. This might happen on some ejabberd servers.
  596.             publishedList = null;
  597.         } catch (XMPPException.XMPPErrorException e) {
  598.             if (e.getStanzaError().getCondition() == StanzaError.Condition.item_not_found) {
  599.                 // Items not found -> items do not exist
  600.                 publishedList = null;
  601.             } else {
  602.                 // Some other error -> throw
  603.                 throw e;
  604.             }
  605.         }
  606.         if (publishedList == null) {
  607.             publishedList = new OmemoDeviceListElement_VAxolotl(Collections.<Integer>emptySet());
  608.         }

  609.         getOmemoStoreBackend().mergeCachedDeviceList(userDevice, userDevice.getJid(), publishedList);

  610.         OmemoCachedDeviceList cachedList = cleanUpDeviceList(userDevice);

  611.         // Republish our deviceId if it is missing from the published list.
  612.         if (!publishedList.getDeviceIds().equals(cachedList.getActiveDevices())) {
  613.             publishDeviceList(connection, new OmemoDeviceListElement_VAxolotl(cachedList));
  614.         }
  615.     }

  616.     /**
  617.      * Add our load the deviceList of the user from cache, delete stale devices if needed, add the users device
  618.      * back if necessary, store the refurbished list in cache and return it.
  619.      *
  620.      * @param userDevice our own OMEMO device
  621.      * @return cleaned device list
  622.      *
  623.      * @throws IOException if an I/O error occurred.
  624.      */
  625.     OmemoCachedDeviceList cleanUpDeviceList(OmemoDevice userDevice) throws IOException {
  626.         OmemoCachedDeviceList cachedDeviceList;

  627.         // Delete stale devices if allowed and necessary
  628.         if (OmemoConfiguration.getDeleteStaleDevices()) {
  629.             cachedDeviceList = deleteStaleDevices(userDevice);
  630.         } else {
  631.             cachedDeviceList = getOmemoStoreBackend().loadCachedDeviceList(userDevice);
  632.         }


  633.         // Add back our device if necessary
  634.         if (!cachedDeviceList.getActiveDevices().contains(userDevice.getDeviceId())) {
  635.             cachedDeviceList.addDevice(userDevice.getDeviceId());
  636.         }

  637.         getOmemoStoreBackend().storeCachedDeviceList(userDevice, userDevice.getJid(), cachedDeviceList);
  638.         return cachedDeviceList;
  639.     }

  640.     /**
  641.      * Refresh and merge device list of contact.
  642.      *
  643.      * @param connection authenticated XMPP connection
  644.      * @param userDevice our OmemoDevice
  645.      * @param contact contact we want to fetch the deviceList from
  646.      * @return cached device list after refresh.
  647.      *
  648.      * @throws InterruptedException if the calling thread was interrupted.
  649.      * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
  650.      * @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
  651.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  652.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  653.      * @throws IOException if an I/O error occurred.
  654.      */
  655.     OmemoCachedDeviceList refreshDeviceList(XMPPConnection connection, OmemoDevice userDevice, BareJid contact)
  656.             throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
  657.             SmackException.NotConnectedException, SmackException.NoResponseException, IOException {
  658.         // refreshOmemoDeviceList;
  659.         OmemoDeviceListElement publishedList;
  660.         try {
  661.             publishedList = fetchDeviceList(connection, contact);
  662.         } catch (PubSubException.NotAPubSubNodeException e) {
  663.             LOGGER.log(Level.WARNING, "Error refreshing deviceList: ", e);
  664.             publishedList = null;
  665.         }
  666.         if (publishedList == null) {
  667.             publishedList = new OmemoDeviceListElement_VAxolotl(Collections.<Integer>emptySet());
  668.         }

  669.         return getOmemoStoreBackend().mergeCachedDeviceList(
  670.                 userDevice, contact, publishedList);
  671.     }

  672.     /**
  673.      * Fetch the bundle of a contact and build a fresh OMEMO session with the contacts device.
  674.      * Note that this builds a fresh session, regardless if we have had a session before or not.
  675.      *
  676.      * @param connection authenticated XMPP connection
  677.      * @param userDevice our OmemoDevice
  678.      * @param contactsDevice OmemoDevice of a contact.
  679.      *
  680.      * @throws CannotEstablishOmemoSessionException if we cannot establish a session (because of missing bundle etc.)
  681.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  682.      * @throws InterruptedException if the calling thread was interrupted.
  683.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  684.      * @throws CorruptedOmemoKeyException if our IdentityKeyPair is corrupted.
  685.      */
  686.     void buildFreshSessionWithDevice(XMPPConnection connection, OmemoDevice userDevice, OmemoDevice contactsDevice)
  687.             throws CannotEstablishOmemoSessionException, SmackException.NotConnectedException, InterruptedException,
  688.             SmackException.NoResponseException, CorruptedOmemoKeyException {

  689.         if (contactsDevice.equals(userDevice)) {
  690.             // Do not build a session with yourself.
  691.             return;
  692.         }

  693.         OmemoBundleElement bundleElement;
  694.         try {
  695.             bundleElement = fetchBundle(connection, contactsDevice);
  696.         } catch (XMPPException.XMPPErrorException | PubSubException.NotALeafNodeException |
  697.                 PubSubException.NotAPubSubNodeException e) {
  698.             throw new CannotEstablishOmemoSessionException(contactsDevice, e);
  699.         }

  700.         // Select random Bundle
  701.         HashMap<Integer, T_Bundle> bundlesList = getOmemoStoreBackend().keyUtil().BUNDLE.bundles(bundleElement, contactsDevice);
  702.         int randomIndex = new Random().nextInt(bundlesList.size());
  703.         T_Bundle randomPreKeyBundle = new ArrayList<>(bundlesList.values()).get(randomIndex);

  704.         // build the session
  705.         OmemoManager omemoManager = OmemoManager.getInstanceFor(connection, userDevice.getDeviceId());
  706.         processBundle(omemoManager, randomPreKeyBundle, contactsDevice);
  707.     }

  708.     /**
  709.      * Build sessions with all devices from the set, we don't have a session with yet.
  710.      * Return the set of all devices we have a session with afterwards.
  711.      *
  712.      * @param connection authenticated XMPP connection
  713.      * @param userDevice our OmemoDevice
  714.      * @param devices set of devices we may want to build a session with if necessary
  715.      * @return set of all devices with sessions
  716.      *
  717.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  718.      * @throws InterruptedException if the calling thread was interrupted.
  719.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  720.      * @throws IOException if an I/O error occurred.
  721.      */
  722.     private Set<OmemoDevice> buildMissingSessionsWithDevices(XMPPConnection connection,
  723.                                                              OmemoDevice userDevice,
  724.                                                              Set<OmemoDevice> devices)
  725.             throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, IOException {

  726.         Set<OmemoDevice> devicesWithSession = new HashSet<>();
  727.         for (OmemoDevice device : devices) {

  728.             if (hasSession(userDevice, device)) {
  729.                 devicesWithSession.add(device);
  730.                 continue;
  731.             }

  732.             try {
  733.                 buildFreshSessionWithDevice(connection, userDevice, device);
  734.                 devicesWithSession.add(device);
  735.             } catch (CannotEstablishOmemoSessionException e) {
  736.                 LOGGER.log(Level.WARNING, userDevice + " cannot establish session with " + device +
  737.                         " because their bundle could not be fetched.", e);
  738.             } catch (CorruptedOmemoKeyException e) {
  739.                 LOGGER.log(Level.WARNING, userDevice + " could not establish session with " + device +
  740.                         "because their bundle seems to be corrupt.", e);
  741.             }

  742.         }

  743.         return devicesWithSession;
  744.     }

  745.     /**
  746.      * Return a set of all devices from the provided set, which trust level is undecided.
  747.      * A device is also considered undecided, if its fingerprint cannot be loaded.
  748.      *
  749.      * @param userDevice our OmemoDevice
  750.      * @param callback OmemoTrustCallback to query the trust decisions from
  751.      * @param devices set of OmemoDevices
  752.      * @return set of OmemoDevices which contains all devices from the set devices, which are undecided
  753.      *
  754.      * @throws IOException if an I/O error occurred.
  755.      */
  756.     private Set<OmemoDevice> getUndecidedDevices(OmemoDevice userDevice, OmemoTrustCallback callback, Set<OmemoDevice> devices) throws IOException {
  757.         Set<OmemoDevice> undecidedDevices = new HashSet<>();

  758.         for (OmemoDevice device : devices) {

  759.             OmemoFingerprint fingerprint;
  760.             try {
  761.                 fingerprint = getOmemoStoreBackend().getFingerprint(userDevice, device);
  762.             } catch (CorruptedOmemoKeyException | NoIdentityKeyException e) {
  763.                 LOGGER.log(Level.WARNING, "Could not load fingerprint of " + device, e);
  764.                 undecidedDevices.add(device);
  765.                 continue;
  766.             }

  767.             if (callback.getTrust(device, fingerprint) == TrustState.undecided) {
  768.                 undecidedDevices.add(device);
  769.             }
  770.         }

  771.         return undecidedDevices;
  772.     }

  773.     /**
  774.      * Return true, if the OmemoManager of userDevice has a session with the contactsDevice.
  775.      *
  776.      * @param userDevice our OmemoDevice.
  777.      * @param contactsDevice OmemoDevice of the contact.
  778.      * @return true if userDevice has session with contactsDevice.
  779.      *
  780.      * @throws IOException if an I/O error occurred.
  781.      */
  782.     private boolean hasSession(OmemoDevice userDevice, OmemoDevice contactsDevice) throws IOException {
  783.         return getOmemoStoreBackend().loadRawSession(userDevice, contactsDevice) != null;
  784.     }

  785.     /**
  786.      * Process a received bundle. Typically, that includes saving keys and building a session.
  787.      *
  788.      * @param omemoManager our OmemoManager
  789.      * @param contactsBundle bundle of the contact
  790.      * @param contactsDevice OmemoDevice of the contact
  791.      *
  792.      * @throws CorruptedOmemoKeyException if the OMEMO key is corrupted.
  793.      */
  794.     protected abstract void processBundle(OmemoManager omemoManager,
  795.                                           T_Bundle contactsBundle,
  796.                                           OmemoDevice contactsDevice)
  797.             throws CorruptedOmemoKeyException;

  798.     /**
  799.      * Returns true, if a rotation of the signed preKey is necessary.
  800.      *
  801.      * @param userDevice our OmemoDevice
  802.      * @return true if rotation is necessary
  803.      *
  804.      * @throws IOException if an I/O error occurred.
  805.      */
  806.     private boolean shouldRotateSignedPreKey(OmemoDevice userDevice) throws IOException {
  807.         if (!OmemoConfiguration.getRenewOldSignedPreKeys()) {
  808.             return false;
  809.         }

  810.         Date now = new Date();
  811.         Date lastRenewal = getOmemoStoreBackend().getDateOfLastSignedPreKeyRenewal(userDevice);

  812.         if (lastRenewal == null) {
  813.             lastRenewal = new Date();
  814.             getOmemoStoreBackend().setDateOfLastSignedPreKeyRenewal(userDevice, lastRenewal);
  815.         }

  816.         long allowedAgeMillis = MILLIS_PER_HOUR * OmemoConfiguration.getRenewOldSignedPreKeysAfterHours();
  817.         return now.getTime() - lastRenewal.getTime() > allowedAgeMillis;
  818.     }

  819.     /**
  820.      * Return a copy of our deviceList, but with stale devices marked as inactive.
  821.      * Never mark our own device as stale.
  822.      * This method ignores {@link OmemoConfiguration#getDeleteStaleDevices()}!
  823.      *
  824.      * In this case, a stale device is one of our devices, from which we haven't received an OMEMO message from
  825.      * for more than {@link OmemoConfiguration#getDeleteStaleDevicesAfterHours()} hours.
  826.      *
  827.      * @param userDevice our OmemoDevice
  828.      * @return our altered deviceList with stale devices marked as inactive.
  829.      *
  830.      * @throws IOException if an I/O error occurred.
  831.      */
  832.     private OmemoCachedDeviceList deleteStaleDevices(OmemoDevice userDevice) throws IOException {
  833.         OmemoCachedDeviceList deviceList = getOmemoStoreBackend().loadCachedDeviceList(userDevice);
  834.         int maxAgeHours = OmemoConfiguration.getDeleteStaleDevicesAfterHours();
  835.         return removeStaleDevicesFromDeviceList(userDevice, userDevice.getJid(), deviceList, maxAgeHours);
  836.     }

  837.     /**
  838.      * Return a copy of the given deviceList of user contact, but with stale devices marked as inactive.
  839.      * Never mark our own device as stale. If we haven't yet received a message from a device, store the current date
  840.      * as last date of message receipt to allow future decisions.
  841.      *
  842.      * A stale device is a device, from which we haven't received an OMEMO message from for more than
  843.      * "maxAgeMillis" milliseconds.
  844.      *
  845.      * @param userDevice our OmemoDevice.
  846.      * @param contact subjects BareJid.
  847.      * @param contactsDeviceList subjects deviceList.
  848.      * @return copy of subjects deviceList with stale devices marked as inactive.
  849.      *
  850.      * @throws IOException if an I/O error occurred.
  851.      */
  852.     private OmemoCachedDeviceList removeStaleDevicesFromDeviceList(OmemoDevice userDevice,
  853.                                                                    BareJid contact,
  854.                                                                    OmemoCachedDeviceList contactsDeviceList,
  855.                                                                    int maxAgeHours) throws IOException {
  856.         OmemoCachedDeviceList deviceList = new OmemoCachedDeviceList(contactsDeviceList); // Don't work on original list.

  857.         // Iterate through original list, but modify copy instead
  858.         for (int deviceId : contactsDeviceList.getActiveDevices()) {
  859.             OmemoDevice device = new OmemoDevice(contact, deviceId);

  860.             Date lastDeviceIdPublication = getOmemoStoreBackend().getDateOfLastDeviceIdPublication(userDevice, device);
  861.             if (lastDeviceIdPublication == null) {
  862.                 lastDeviceIdPublication = new Date();
  863.                 getOmemoStoreBackend().setDateOfLastDeviceIdPublication(userDevice, device, lastDeviceIdPublication);
  864.             }

  865.             Date lastMessageReceived = getOmemoStoreBackend().getDateOfLastReceivedMessage(userDevice, device);
  866.             if (lastMessageReceived == null) {
  867.                 lastMessageReceived = new Date();
  868.                 getOmemoStoreBackend().setDateOfLastReceivedMessage(userDevice, device, lastMessageReceived);
  869.             }

  870.             boolean stale = isStale(userDevice, device, lastDeviceIdPublication, maxAgeHours);
  871.             stale &= isStale(userDevice, device, lastMessageReceived, maxAgeHours);

  872.             if (stale) {
  873.                 deviceList.addInactiveDevice(deviceId);
  874.             }
  875.         }
  876.         return deviceList;
  877.     }


  878.     /**
  879.      * Remove our device from the collection of devices.
  880.      *
  881.      * @param userDevice our OmemoDevice
  882.      * @param devices collection of OmemoDevices
  883.      */
  884.     static void removeOurDevice(OmemoDevice userDevice, Collection<OmemoDevice> devices) {
  885.         if (devices.contains(userDevice)) {
  886.             devices.remove(userDevice);
  887.         }
  888.     }

  889.     /**
  890.      * Determine, whether another one of *our* devices is stale or not.
  891.      *
  892.      * @param userDevice our omemoDevice
  893.      * @param subject another one of our devices
  894.      * @param lastReceipt date of last received message from that device
  895.      * @param maxAgeHours threshold
  896.      *
  897.      * @return true if the subject device is considered stale
  898.      */
  899.     static boolean isStale(OmemoDevice userDevice, OmemoDevice subject, Date lastReceipt, int maxAgeHours) {
  900.         if (userDevice.equals(subject)) {
  901.             return false;
  902.         }

  903.         if (lastReceipt == null) {
  904.             return false;
  905.         }

  906.         long maxAgeMillis = MILLIS_PER_HOUR * maxAgeHours;
  907.         Date now = new Date();

  908.         return now.getTime() - lastReceipt.getTime() > maxAgeMillis;
  909.     }

  910.     /**
  911.      * Gullible TrustCallback, which returns all queried identities as trusted.
  912.      * This is only used for insensitive OMEMO messages like RatchetUpdateMessages.
  913.      * DO NOT USE THIS FOR ANYTHING ELSE!
  914.      */
  915.     private static final OmemoTrustCallback gullibleTrustCallback = new OmemoTrustCallback() {
  916.         @Override
  917.         public TrustState getTrust(OmemoDevice device, OmemoFingerprint fingerprint) {
  918.             return TrustState.trusted;
  919.         }

  920.         @Override
  921.         public void setTrust(OmemoDevice device, OmemoFingerprint fingerprint, TrustState state) {
  922.             // Not needed
  923.         }
  924.     };

  925.     /**
  926.      * Decrypt a possible OMEMO encrypted messages in a {@link MamManager.MamQuery}.
  927.      * The returned list contains wrappers that either hold an {@link OmemoMessage} in case the message was decrypted
  928.      * properly, otherwise it contains the message itself.
  929.      *
  930.      * @param managerGuard authenticated OmemoManager.
  931.      * @param mamQuery Mam archive query
  932.      * @return list of {@link MessageOrOmemoMessage MessageOrOmemoMessages}.
  933.      *
  934.      * @throws IOException if an I/O error occurred.
  935.      */
  936.     List<MessageOrOmemoMessage> decryptMamQueryResult(OmemoManager.LoggedInOmemoManager managerGuard,
  937.                                                       MamManager.MamQuery mamQuery) throws IOException {
  938.         List<MessageOrOmemoMessage> result = new ArrayList<>();
  939.         for (Message message : mamQuery.getMessages()) {
  940.             if (OmemoManager.stanzaContainsOmemoElement(message)) {
  941.                 OmemoElement element =
  942.                         (OmemoElement) message.getExtensionElement(OmemoElement.NAME_ENCRYPTED, OmemoConstants.OMEMO_NAMESPACE_V_AXOLOTL);
  943.                 // Decrypt OMEMO messages
  944.                 try {
  945.                     OmemoMessage.Received omemoMessage = decryptMessage(managerGuard, message.getFrom().asBareJid(), element);
  946.                     result.add(new MessageOrOmemoMessage(omemoMessage));
  947.                 } catch (NoRawSessionException | CorruptedOmemoKeyException | CryptoFailedException e) {
  948.                     LOGGER.log(Level.WARNING, "decryptMamQueryResult failed to decrypt message from "
  949.                             + message.getFrom() + " due to corrupted session/key: " + e.getMessage());
  950.                     result.add(new MessageOrOmemoMessage(message));
  951.                 }
  952.             } else {
  953.                 // Wrap cleartext messages
  954.                 result.add(new MessageOrOmemoMessage(message));
  955.             }
  956.         }

  957.         return result;
  958.     }


  959.     @Override
  960.     public void onOmemoCarbonCopyReceived(CarbonExtension.Direction direction,
  961.                                           Message carbonCopy,
  962.                                           Message wrappingMessage,
  963.                                           OmemoManager.LoggedInOmemoManager managerGuard) throws IOException {
  964.         OmemoManager manager = managerGuard.get();
  965.         // Avoid the ratchet being manipulated and the bundle being published multiple times simultaneously
  966.         synchronized (manager) {
  967.             OmemoDevice userDevice = manager.getOwnDevice();
  968.             OmemoElement element = (OmemoElement) carbonCopy.getExtensionElement(OmemoElement.NAME_ENCRYPTED, OmemoElement_VAxolotl.NAMESPACE);
  969.             if (element == null) {
  970.                 return;
  971.             }

  972.             OmemoMessage.Received decrypted;
  973.             BareJid sender = carbonCopy.getFrom().asBareJid();

  974.             try {
  975.                 decrypted = decryptMessage(managerGuard, sender, element);
  976.                 manager.notifyOmemoCarbonCopyReceived(direction, carbonCopy, wrappingMessage, decrypted);

  977.                 if (decrypted.isPreKeyMessage() && OmemoConfiguration.getCompleteSessionWithEmptyMessage()) {
  978.                     LOGGER.log(Level.FINE, "Received a preKeyMessage in a carbon copy from " + decrypted.getSenderDevice() + ".\n" +
  979.                             "Complete the session by sending an empty response message.");
  980.                     try {
  981.                         sendRatchetUpdate(managerGuard, decrypted.getSenderDevice());
  982.                     } catch (CannotEstablishOmemoSessionException e) {
  983.                         throw new AssertionError("Since we successfully received a message, we MUST be able to " +
  984.                                 "establish a session. " + e);
  985.                     } catch (NoSuchAlgorithmException | InterruptedException | SmackException.NotConnectedException | SmackException.NoResponseException e) {
  986.                         LOGGER.log(Level.WARNING, "Cannot send a ratchet update message.", e);
  987.                     }
  988.                 }
  989.             } catch (NoRawSessionException e) {
  990.                 OmemoDevice device = e.getDeviceWithoutSession();
  991.                 LOGGER.log(Level.WARNING, "No raw session found for contact " + device + ". ", e);

  992.                 if (OmemoConfiguration.getRepairBrokenSessionsWithPreKeyMessages()) {
  993.                     repairBrokenSessionWithPreKeyMessage(managerGuard, device);
  994.                 }
  995.             } catch (CorruptedOmemoKeyException | CryptoFailedException e) {
  996.                 LOGGER.log(Level.WARNING, "Could not decrypt incoming carbon copy: ", e);
  997.             }

  998.             // Upload fresh bundle.
  999.             if (getOmemoStoreBackend().loadOmemoPreKeys(userDevice).size() < OmemoConstants.PRE_KEY_COUNT_PER_BUNDLE) {
  1000.                 LOGGER.log(Level.FINE, "We used up a preKey. Upload a fresh bundle.");
  1001.                 try {
  1002.                     getOmemoStoreBackend().replenishKeys(userDevice);
  1003.                     OmemoBundleElement bundleElement = getOmemoStoreBackend().packOmemoBundle(userDevice);
  1004.                     publishBundle(manager.getConnection(), userDevice, bundleElement);
  1005.                 } catch (CorruptedOmemoKeyException | InterruptedException | SmackException.NoResponseException
  1006.                         | SmackException.NotConnectedException | XMPPException.XMPPErrorException
  1007.                         | NotALeafNodeException e) {
  1008.                     LOGGER.log(Level.WARNING, "Could not republish replenished bundle.", e);
  1009.                 }
  1010.             }
  1011.         }
  1012.     }

  1013.     @Override
  1014.     public void onOmemoMessageStanzaReceived(Stanza stanza, OmemoManager.LoggedInOmemoManager managerGuard) throws IOException {
  1015.         OmemoManager manager = managerGuard.get();
  1016.         // Avoid the ratchet being manipulated and the bundle being published multiple times simultaneously
  1017.         synchronized (manager) {
  1018.             OmemoDevice userDevice = manager.getOwnDevice();
  1019.             OmemoElement element = (OmemoElement) stanza.getExtensionElement(OmemoElement.NAME_ENCRYPTED, OmemoElement_VAxolotl.NAMESPACE);
  1020.             if (element == null) {
  1021.                 return;
  1022.             }

  1023.             OmemoMessage.Received decrypted;
  1024.             BareJid sender;

  1025.             try {
  1026.                 MultiUserChat muc = getMuc(manager.getConnection(), stanza.getFrom());
  1027.                 if (muc != null) {
  1028.                     Occupant occupant = muc.getOccupant(stanza.getFrom().asEntityFullJidIfPossible());
  1029.                     if (occupant == null) {
  1030.                         LOGGER.log(Level.WARNING, "Cannot decrypt OMEMO MUC message; MUC Occupant is null.");
  1031.                         return;
  1032.                     }
  1033.                     Jid occupantJid = occupant.getJid();

  1034.                     if (occupantJid == null) {
  1035.                         LOGGER.log(Level.WARNING, "Cannot decrypt OMEMO MUC message; Senders Jid is null. " +
  1036.                                 stanza.getFrom());
  1037.                         return;
  1038.                     }

  1039.                     sender = occupantJid.asBareJid();

  1040.                     // try is for this
  1041.                     decrypted = decryptMessage(managerGuard, sender, element);
  1042.                     manager.notifyOmemoMucMessageReceived(muc, stanza, decrypted);

  1043.                 } else {
  1044.                     sender = stanza.getFrom().asBareJid();

  1045.                     // and this
  1046.                     decrypted = decryptMessage(managerGuard, sender, element);
  1047.                     manager.notifyOmemoMessageReceived(stanza, decrypted);
  1048.                 }

  1049.                 if (decrypted.isPreKeyMessage() && OmemoConfiguration.getCompleteSessionWithEmptyMessage()) {
  1050.                     LOGGER.log(Level.FINE, "Received a preKeyMessage from " + decrypted.getSenderDevice() + ".\n" +
  1051.                             "Complete the session by sending an empty response message.");
  1052.                     try {
  1053.                         sendRatchetUpdate(managerGuard, decrypted.getSenderDevice());
  1054.                     } catch (CannotEstablishOmemoSessionException e) {
  1055.                         throw new AssertionError("Since we successfully received a message, we MUST be able to " +
  1056.                                 "establish a session. " + e);
  1057.                     } catch (NoSuchAlgorithmException | InterruptedException | SmackException.NotConnectedException | SmackException.NoResponseException e) {
  1058.                         LOGGER.log(Level.WARNING, "Cannot send a ratchet update message.", e);
  1059.                     }
  1060.                 }
  1061.             } catch (NoRawSessionException e) {
  1062.                 OmemoDevice device = e.getDeviceWithoutSession();
  1063.                 LOGGER.log(Level.WARNING, "No raw session found for contact " + device + ". ", e);

  1064.                 if (OmemoConfiguration.getRepairBrokenSessionsWithPreKeyMessages()) {
  1065.                     repairBrokenSessionWithPreKeyMessage(managerGuard, device);
  1066.                 }
  1067.             } catch (CorruptedOmemoKeyException | CryptoFailedException e) {
  1068.                 LOGGER.log(Level.WARNING, "Could not decrypt incoming message: ", e);
  1069.             }

  1070.             // Upload fresh bundle.
  1071.             if (getOmemoStoreBackend().loadOmemoPreKeys(userDevice).size() < OmemoConstants.PRE_KEY_COUNT_PER_BUNDLE) {
  1072.                 LOGGER.log(Level.FINE, "We used up a preKey. Upload a fresh bundle.");
  1073.                 try {
  1074.                     getOmemoStoreBackend().replenishKeys(userDevice);
  1075.                     OmemoBundleElement bundleElement = getOmemoStoreBackend().packOmemoBundle(userDevice);
  1076.                     publishBundle(manager.getConnection(), userDevice, bundleElement);
  1077.                 } catch (CorruptedOmemoKeyException | InterruptedException | SmackException.NoResponseException
  1078.                         | SmackException.NotConnectedException | XMPPException.XMPPErrorException
  1079.                         | NotALeafNodeException e) {
  1080.                     LOGGER.log(Level.WARNING, "Could not republish replenished bundle.", e);
  1081.                 }
  1082.             }
  1083.         }
  1084.     }

  1085.     /**
  1086.      * Decrypt the OmemoElement inside the given Stanza and return it.
  1087.      * Return null if something goes wrong.
  1088.      *
  1089.      * @param stanza stanza
  1090.      * @param managerGuard authenticated OmemoManager
  1091.      * @return decrypted OmemoMessage or null
  1092.      *
  1093.      * @throws IOException if an I/O error occurred.
  1094.      */
  1095.     OmemoMessage.Received decryptStanza(Stanza stanza, OmemoManager.LoggedInOmemoManager managerGuard) throws IOException {
  1096.         OmemoManager manager = managerGuard.get();
  1097.         // Avoid the ratchet being manipulated and the bundle being published multiple times simultaneously
  1098.         synchronized (manager) {
  1099.             OmemoDevice userDevice = manager.getOwnDevice();
  1100.             OmemoElement element = (OmemoElement) stanza.getExtensionElement(OmemoElement.NAME_ENCRYPTED, OmemoElement_VAxolotl.NAMESPACE);
  1101.             if (element == null) {
  1102.                 return null;
  1103.             }

  1104.             OmemoMessage.Received decrypted = null;
  1105.             BareJid sender;

  1106.             try {
  1107.                 MultiUserChat muc = getMuc(manager.getConnection(), stanza.getFrom());
  1108.                 if (muc != null) {
  1109.                     Occupant occupant = muc.getOccupant(stanza.getFrom().asEntityFullJidIfPossible());
  1110.                     Jid occupantJid = occupant.getJid();

  1111.                     if (occupantJid == null) {
  1112.                         LOGGER.log(Level.WARNING, "MUC message received, but there is no way to retrieve the senders Jid. " +
  1113.                                 stanza.getFrom());
  1114.                         return null;
  1115.                     }

  1116.                     sender = occupantJid.asBareJid();

  1117.                     // try is for this
  1118.                     decrypted = decryptMessage(managerGuard, sender, element);

  1119.                 } else {
  1120.                     sender = stanza.getFrom().asBareJid();

  1121.                     // and this
  1122.                     decrypted = decryptMessage(managerGuard, sender, element);
  1123.                 }

  1124.                 if (decrypted.isPreKeyMessage() && OmemoConfiguration.getCompleteSessionWithEmptyMessage()) {
  1125.                     LOGGER.log(Level.FINE, "Received a preKeyMessage from " + decrypted.getSenderDevice() + ".\n" +
  1126.                             "Complete the session by sending an empty response message.");
  1127.                     try {
  1128.                         sendRatchetUpdate(managerGuard, decrypted.getSenderDevice());
  1129.                     } catch (CannotEstablishOmemoSessionException e) {
  1130.                         throw new AssertionError("Since we successfully received a message, we MUST be able to " +
  1131.                                 "establish a session. " + e);
  1132.                     } catch (NoSuchAlgorithmException | InterruptedException | SmackException.NotConnectedException | SmackException.NoResponseException e) {
  1133.                         LOGGER.log(Level.WARNING, "Cannot send a ratchet update message.", e);
  1134.                     }
  1135.                 }
  1136.             } catch (NoRawSessionException e) {
  1137.                 OmemoDevice device = e.getDeviceWithoutSession();
  1138.                 LOGGER.log(Level.WARNING, "No raw session found for contact " + device + ". ", e);

  1139.             } catch (CorruptedOmemoKeyException | CryptoFailedException e) {
  1140.                 LOGGER.log(Level.WARNING, "Could not decrypt incoming message: ", e);
  1141.             }

  1142.             // Upload fresh bundle.
  1143.             if (getOmemoStoreBackend().loadOmemoPreKeys(userDevice).size() < OmemoConstants.PRE_KEY_COUNT_PER_BUNDLE) {
  1144.                 LOGGER.log(Level.FINE, "We used up a preKey. Upload a fresh bundle.");
  1145.                 try {
  1146.                     getOmemoStoreBackend().replenishKeys(userDevice);
  1147.                     OmemoBundleElement bundleElement = getOmemoStoreBackend().packOmemoBundle(userDevice);
  1148.                     publishBundle(manager.getConnection(), userDevice, bundleElement);
  1149.                 } catch (CorruptedOmemoKeyException | InterruptedException | SmackException.NoResponseException
  1150.                         | SmackException.NotConnectedException | XMPPException.XMPPErrorException
  1151.                         | NotALeafNodeException e) {
  1152.                     LOGGER.log(Level.WARNING, "Could not republish replenished bundle.", e);
  1153.                 }
  1154.             }
  1155.             return decrypted;
  1156.         }
  1157.     }

  1158.     /**
  1159.      * Fetch and process a fresh bundle and send an empty preKeyMessage in order to establish a fresh session.
  1160.      *
  1161.      * @param managerGuard authenticated OmemoManager.
  1162.      * @param brokenDevice device which session broke.
  1163.      *
  1164.      * @throws IOException if an I/O error occurred.
  1165.      */
  1166.     private void repairBrokenSessionWithPreKeyMessage(OmemoManager.LoggedInOmemoManager managerGuard,
  1167.                                                       OmemoDevice brokenDevice) throws IOException {

  1168.         LOGGER.log(Level.WARNING, "Attempt to repair the session by sending a fresh preKey message to "
  1169.                 + brokenDevice);
  1170.         OmemoManager manager = managerGuard.get();
  1171.         try {
  1172.             // Create fresh session and send new preKeyMessage.
  1173.             buildFreshSessionWithDevice(manager.getConnection(), manager.getOwnDevice(), brokenDevice);
  1174.             sendRatchetUpdate(managerGuard, brokenDevice);

  1175.         } catch (CannotEstablishOmemoSessionException | CorruptedOmemoKeyException e) {
  1176.             LOGGER.log(Level.WARNING, "Unable to repair session with " + brokenDevice, e);
  1177.         } catch (SmackException.NotConnectedException | InterruptedException | SmackException.NoResponseException e) {
  1178.             LOGGER.log(Level.WARNING, "Could not fetch fresh bundle for " + brokenDevice, e);
  1179.         } catch (CryptoFailedException | NoSuchAlgorithmException e) {
  1180.             LOGGER.log(Level.WARNING, "Could not create PreKeyMessage", e);
  1181.         }
  1182.     }

  1183.     /**
  1184.      * Send an empty OMEMO message to contactsDevice in order to forward the ratchet.
  1185.      *
  1186.      * @param managerGuard OMEMO manager
  1187.      * @param contactsDevice contacts OMEMO device
  1188.      *
  1189.      * @throws CorruptedOmemoKeyException if our or their OMEMO key is corrupted.
  1190.      * @throws InterruptedException if the calling thread was interrupted.
  1191.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  1192.      * @throws NoSuchAlgorithmException if AES encryption fails
  1193.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  1194.      * @throws CryptoFailedException if encryption fails (should not happen though, but who knows...)
  1195.      * @throws CannotEstablishOmemoSessionException if we cannot establish a session with contactsDevice.
  1196.      * @throws IOException if an I/O error occurred.
  1197.      */
  1198.     private void sendRatchetUpdate(OmemoManager.LoggedInOmemoManager managerGuard, OmemoDevice contactsDevice)
  1199.             throws CorruptedOmemoKeyException, InterruptedException, SmackException.NoResponseException,
  1200.             NoSuchAlgorithmException, SmackException.NotConnectedException, CryptoFailedException,
  1201.             CannotEstablishOmemoSessionException, IOException {

  1202.         OmemoManager manager = managerGuard.get();
  1203.         OmemoElement ratchetUpdate = createRatchetUpdateElement(managerGuard, contactsDevice);

  1204.         XMPPConnection connection = manager.getConnection();
  1205.         Message message = connection.getStanzaFactory().buildMessageStanza()
  1206.                 .to(contactsDevice.getJid())
  1207.                 .addExtension(ratchetUpdate)
  1208.                 .build();
  1209.         connection.sendStanza(message);
  1210.     }

  1211.     /**
  1212.      * Return the joined MUC with EntityBareJid jid, or null if its not a room and/or not joined.
  1213.      *
  1214.      * @param connection xmpp connection
  1215.      * @param jid jid (presumably) of the MUC
  1216.      * @return MultiUserChat or null if not a MUC.
  1217.      */
  1218.     private static MultiUserChat getMuc(XMPPConnection connection, Jid jid) {
  1219.         EntityBareJid ebj = jid.asEntityBareJidIfPossible();
  1220.         if (ebj == null) {
  1221.             return null;
  1222.         }

  1223.         MultiUserChatManager mucm = MultiUserChatManager.getInstanceFor(connection);
  1224.         Set<EntityBareJid> joinedRooms = mucm.getJoinedRooms();
  1225.         if (joinedRooms.contains(ebj)) {
  1226.             return mucm.getMultiUserChat(ebj);
  1227.         }

  1228.         return null;
  1229.     }

  1230.     /**
  1231.      * Publish a new DeviceList with just our device in it.
  1232.      *
  1233.      * @param managerGuard authenticated OmemoManager.
  1234.      *
  1235.      * @throws InterruptedException if the calling thread was interrupted.
  1236.      * @throws XMPPException.XMPPErrorException if there was an XMPP error returned.
  1237.      * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
  1238.      * @throws SmackException.NoResponseException if there was no response from the remote entity.
  1239.      * @throws IOException if an I/O error occurred.
  1240.      * @throws NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node.
  1241.      */
  1242.     public void purgeDeviceList(OmemoManager.LoggedInOmemoManager managerGuard)
  1243.             throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException,
  1244.             SmackException.NoResponseException, IOException, NotALeafNodeException {

  1245.         OmemoManager omemoManager = managerGuard.get();
  1246.         OmemoDevice userDevice = omemoManager.getOwnDevice();

  1247.         OmemoDeviceListElement_VAxolotl newList =
  1248.                 new OmemoDeviceListElement_VAxolotl(Collections.singleton(userDevice.getDeviceId()));

  1249.         // Merge list
  1250.         getOmemoStoreBackend().mergeCachedDeviceList(userDevice, userDevice.getJid(), newList);

  1251.         OmemoService.publishDeviceList(omemoManager.getConnection(), newList);
  1252.     }
  1253. }