001/** 002 * 003 * Copyright 2017 Paul Schaub, 2019 Florian Schmaus 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.Crypto.KEYLENGTH; 020import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYTYPE; 021 022import java.io.IOException; 023import java.security.InvalidAlgorithmParameterException; 024import java.security.InvalidKeyException; 025import java.security.NoSuchAlgorithmException; 026import java.util.ArrayList; 027import java.util.Collection; 028import java.util.Collections; 029import java.util.Date; 030import java.util.HashMap; 031import java.util.HashSet; 032import java.util.List; 033import java.util.Random; 034import java.util.Set; 035import java.util.logging.Level; 036import java.util.logging.Logger; 037 038import javax.crypto.BadPaddingException; 039import javax.crypto.IllegalBlockSizeException; 040import javax.crypto.NoSuchPaddingException; 041 042import org.jivesoftware.smack.SmackException; 043import org.jivesoftware.smack.XMPPConnection; 044import org.jivesoftware.smack.XMPPException; 045import org.jivesoftware.smack.packet.Message; 046import org.jivesoftware.smack.packet.Stanza; 047import org.jivesoftware.smack.packet.StanzaError; 048 049import org.jivesoftware.smackx.carbons.packet.CarbonExtension; 050import org.jivesoftware.smackx.mam.MamManager; 051import org.jivesoftware.smackx.muc.MultiUserChat; 052import org.jivesoftware.smackx.muc.MultiUserChatManager; 053import org.jivesoftware.smackx.muc.Occupant; 054import org.jivesoftware.smackx.omemo.element.OmemoBundleElement; 055import org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement; 056import org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement_VAxolotl; 057import org.jivesoftware.smackx.omemo.element.OmemoElement; 058import org.jivesoftware.smackx.omemo.element.OmemoElement_VAxolotl; 059import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException; 060import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException; 061import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException; 062import org.jivesoftware.smackx.omemo.exceptions.NoIdentityKeyException; 063import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException; 064import org.jivesoftware.smackx.omemo.exceptions.ReadOnlyDeviceException; 065import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException; 066import org.jivesoftware.smackx.omemo.exceptions.UntrustedOmemoIdentityException; 067import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag; 068import org.jivesoftware.smackx.omemo.internal.OmemoCachedDeviceList; 069import org.jivesoftware.smackx.omemo.internal.OmemoDevice; 070import org.jivesoftware.smackx.omemo.internal.listener.OmemoCarbonCopyStanzaReceivedListener; 071import org.jivesoftware.smackx.omemo.internal.listener.OmemoMessageStanzaReceivedListener; 072import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint; 073import org.jivesoftware.smackx.omemo.trust.OmemoTrustCallback; 074import org.jivesoftware.smackx.omemo.trust.TrustState; 075import org.jivesoftware.smackx.omemo.util.MessageOrOmemoMessage; 076import org.jivesoftware.smackx.omemo.util.OmemoConstants; 077import org.jivesoftware.smackx.omemo.util.OmemoMessageBuilder; 078import org.jivesoftware.smackx.pep.PepManager; 079import org.jivesoftware.smackx.pubsub.LeafNode; 080import org.jivesoftware.smackx.pubsub.PayloadItem; 081import org.jivesoftware.smackx.pubsub.PubSubException; 082import org.jivesoftware.smackx.pubsub.PubSubException.NotALeafNodeException; 083import org.jivesoftware.smackx.pubsub.PubSubManager; 084 085import org.jxmpp.jid.BareJid; 086import org.jxmpp.jid.EntityBareJid; 087import org.jxmpp.jid.Jid; 088 089/** 090 * This class contains OMEMO related logic and registers listeners etc. 091 * 092 * @param <T_IdKeyPair> IdentityKeyPair class 093 * @param <T_IdKey> IdentityKey class 094 * @param <T_PreKey> PreKey class 095 * @param <T_SigPreKey> SignedPreKey class 096 * @param <T_Sess> Session class 097 * @param <T_Addr> Address class 098 * @param <T_ECPub> Elliptic Curve PublicKey class 099 * @param <T_Bundle> Bundle class 100 * @param <T_Ciph> Cipher class 101 * 102 * @author Paul Schaub 103 */ 104public abstract class OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> 105 implements OmemoCarbonCopyStanzaReceivedListener, OmemoMessageStanzaReceivedListener { 106 107 protected static final Logger LOGGER = Logger.getLogger(OmemoService.class.getName()); 108 109 private static final long MILLIS_PER_HOUR = 1000L * 60 * 60; 110 111 private static OmemoService<?, ?, ?, ?, ?, ?, ?, ?, ?> INSTANCE; 112 113 private OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore; 114 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<>(); 115 116 protected OmemoService() { 117 118 } 119 120 /** 121 * Return the singleton instance of this class. When no instance is set, throw an IllegalStateException instead. 122 * 123 * @return instance. 124 */ 125 public static OmemoService<?, ?, ?, ?, ?, ?, ?, ?, ?> getInstance() { 126 if (INSTANCE == null) { 127 throw new IllegalStateException("No OmemoService registered"); 128 } 129 return INSTANCE; 130 } 131 132 /** 133 * Set singleton instance. Throws an IllegalStateException, if there is already a service set as instance. 134 * 135 * @param omemoService instance 136 */ 137 protected static void setInstance(OmemoService<?, ?, ?, ?, ?, ?, ?, ?, ?> omemoService) { 138 if (INSTANCE != null) { 139 throw new IllegalStateException("An OmemoService is already registered"); 140 } 141 INSTANCE = omemoService; 142 } 143 144 /** 145 * Returns true, if an instance of the service singleton is set. Otherwise return false. 146 * 147 * @return true, if instance is not null. 148 */ 149 public static boolean isServiceRegistered() { 150 return INSTANCE != null; 151 } 152 153 /** 154 * Return the used omemoStore backend. 155 * If there is no store backend set yet, set the default one (typically a file-based one). 156 * 157 * @return omemoStore backend 158 */ 159 public OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> 160 getOmemoStoreBackend() { 161 if (omemoStore == null) { 162 omemoStore = createDefaultOmemoStoreBackend(); 163 } 164 return omemoStore; 165 } 166 167 /** 168 * Set an omemoStore as backend. Throws an IllegalStateException, if there is already a backend set. 169 * 170 * @param omemoStore store. 171 */ 172 @SuppressWarnings("unused") 173 public void setOmemoStoreBackend( 174 OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore) { 175 if (this.omemoStore != null) { 176 throw new IllegalStateException("An OmemoStore backend has already been set."); 177 } 178 this.omemoStore = omemoStore; 179 } 180 181 /** 182 * Create a default OmemoStore object. 183 * 184 * @return default omemoStore. 185 */ 186 protected abstract OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> 187 createDefaultOmemoStoreBackend(); 188 189 /** 190 * Return a new instance of the OMEMO ratchet. 191 * The ratchet is internally used to encrypt/decrypt message keys. 192 * 193 * @param manager OmemoManager 194 * @param store OmemoStore 195 * @return instance of the OmemoRatchet 196 */ 197 protected abstract OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> 198 instantiateOmemoRatchet(OmemoManager manager, 199 OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> store); 200 201 /** 202 * Return the deposited instance of the OmemoRatchet for the given manager. 203 * If there is none yet, create a new one, deposit it and return it. 204 * 205 * @param manager OmemoManager we want to have the ratchet for. 206 * @return OmemoRatchet instance 207 */ 208 protected OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> 209 getOmemoRatchet(OmemoManager manager) { 210 OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> 211 omemoRatchet = omemoRatchets.get(manager); 212 if (omemoRatchet == null) { 213 omemoRatchet = instantiateOmemoRatchet(manager, omemoStore); 214 omemoRatchets.put(manager, omemoRatchet); 215 } 216 return omemoRatchet; 217 } 218 219 /** 220 * Instantiate and deposit a Ratchet for the given OmemoManager. 221 * 222 * @param manager manager. 223 */ 224 void registerRatchetForManager(OmemoManager manager) { 225 omemoRatchets.put(manager, instantiateOmemoRatchet(manager, getOmemoStoreBackend())); 226 } 227 228 /** 229 * Initialize OMEMO functionality for the given {@link OmemoManager}. 230 * 231 * @param managerGuard OmemoManager we'd like to initialize. 232 * 233 * @throws InterruptedException if the calling thread was interrupted. 234 * @throws CorruptedOmemoKeyException if the OMEMO key is corrupted. 235 * @throws XMPPException.XMPPErrorException if there was an XMPP error returned. 236 * @throws SmackException.NotConnectedException if the XMPP connection is not connected. 237 * @throws SmackException.NoResponseException if there was no response from the remote entity. 238 * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node. 239 * @throws IOException if an I/O error occurred. 240 */ 241 void init(OmemoManager.LoggedInOmemoManager managerGuard) 242 throws InterruptedException, CorruptedOmemoKeyException, XMPPException.XMPPErrorException, 243 SmackException.NotConnectedException, SmackException.NoResponseException, 244 PubSubException.NotALeafNodeException, IOException { 245 246 OmemoManager manager = managerGuard.get(); 247 OmemoDevice userDevice = manager.getOwnDevice(); 248 249 // Create new keys if necessary and publish to the server. 250 getOmemoStoreBackend().replenishKeys(userDevice); 251 252 // Rotate signed preKey if necessary. 253 if (shouldRotateSignedPreKey(userDevice)) { 254 getOmemoStoreBackend().changeSignedPreKey(userDevice); 255 } 256 257 // Pack and publish bundle 258 OmemoBundleElement bundle = getOmemoStoreBackend().packOmemoBundle(userDevice); 259 publishBundle(manager.getConnection(), userDevice, bundle); 260 261 // Fetch device list and republish deviceId if necessary 262 refreshAndRepublishDeviceList(manager.getConnection(), userDevice); 263 } 264 265 /** 266 * Create an empty OMEMO message, which is used to forward the ratchet of the recipient. 267 * This message type is typically used to create stable sessions. 268 * Note that trust decisions are ignored for the creation of this message. 269 * 270 * @param managerGuard Logged in OmemoManager 271 * @param contactsDevice OmemoDevice of the contact 272 * @return ratchet update message 273 * 274 * @throws NoSuchAlgorithmException if AES algorithms are not supported on this system. 275 * @throws InterruptedException if the calling thread was interrupted. 276 * @throws SmackException.NoResponseException if there was no response from the remote entity. 277 * @throws CorruptedOmemoKeyException if our IdentityKeyPair is corrupted. 278 * @throws SmackException.NotConnectedException if the XMPP connection is not connected. 279 * @throws CannotEstablishOmemoSessionException if session negotiation fails. 280 * @throws IOException if an I/O error occurred. 281 */ 282 OmemoElement createRatchetUpdateElement(OmemoManager.LoggedInOmemoManager managerGuard, 283 OmemoDevice contactsDevice) 284 throws InterruptedException, SmackException.NoResponseException, CorruptedOmemoKeyException, 285 SmackException.NotConnectedException, CannotEstablishOmemoSessionException, NoSuchAlgorithmException, 286 CryptoFailedException, IOException { 287 288 OmemoManager manager = managerGuard.get(); 289 OmemoDevice userDevice = manager.getOwnDevice(); 290 291 if (contactsDevice.equals(userDevice)) { 292 throw new IllegalArgumentException("\"Thou shall not update thy own ratchet!\" - William Shakespeare"); 293 } 294 295 // Establish session if necessary 296 if (!hasSession(userDevice, contactsDevice)) { 297 buildFreshSessionWithDevice(manager.getConnection(), userDevice, contactsDevice); 298 } 299 300 // Generate fresh AES key and IV 301 byte[] messageKey = OmemoMessageBuilder.generateKey(KEYTYPE, KEYLENGTH); 302 byte[] iv = OmemoMessageBuilder.generateIv(); 303 304 // Create message builder 305 OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> builder; 306 try { 307 builder = new OmemoMessageBuilder<>(userDevice, gullibleTrustCallback, getOmemoRatchet(manager), 308 messageKey, iv, null); 309 } catch (InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException e) { 310 throw new CryptoFailedException(e); 311 } 312 313 // Add recipient 314 try { 315 builder.addRecipient(contactsDevice); 316 } catch (UndecidedOmemoIdentityException | UntrustedOmemoIdentityException e) { 317 throw new AssertionError("Gullible Trust Callback reported undecided or untrusted device, " + 318 "even though it MUST NOT do that."); 319 } catch (NoIdentityKeyException e) { 320 throw new AssertionError("We MUST have an identityKey for " + contactsDevice + " since we built a session." + e); 321 } 322 323 // Note: We don't need to update our message counter for a ratchet update message. 324 325 return builder.finish(); 326 } 327 328 /** 329 * Encrypt a message with a messageKey and an IV and create an OmemoMessage from it. 330 * 331 * @param managerGuard authenticated OmemoManager 332 * @param contactsDevices set of recipient OmemoDevices 333 * @param messageKey AES key to encrypt the message 334 * @param iv iv to be used with the messageKey 335 * @return OmemoMessage object which contains the OmemoElement and some information. 336 * 337 * @throws SmackException.NotConnectedException if the XMPP connection is not connected. 338 * @throws InterruptedException if the calling thread was interrupted. 339 * @throws SmackException.NoResponseException if there was no response from the remote entity. 340 * @throws UndecidedOmemoIdentityException if the list of recipient devices contains undecided devices 341 * @throws CryptoFailedException if we are lacking some crypto primitives 342 * @throws IOException if an I/O error occurred. 343 */ 344 private OmemoMessage.Sent encrypt(OmemoManager.LoggedInOmemoManager managerGuard, 345 Set<OmemoDevice> contactsDevices, 346 byte[] messageKey, 347 byte[] iv, 348 String message) 349 throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, 350 UndecidedOmemoIdentityException, CryptoFailedException, IOException { 351 352 OmemoManager manager = managerGuard.get(); 353 OmemoDevice userDevice = manager.getOwnDevice(); 354 355 // Do not encrypt for our own device. 356 removeOurDevice(userDevice, contactsDevices); 357 358 buildMissingSessionsWithDevices(manager.getConnection(), userDevice, contactsDevices); 359 360 Set<OmemoDevice> undecidedDevices = getUndecidedDevices(userDevice, manager.getTrustCallback(), contactsDevices); 361 if (!undecidedDevices.isEmpty()) { 362 throw new UndecidedOmemoIdentityException(undecidedDevices); 363 } 364 365 // Keep track of skipped devices 366 HashMap<OmemoDevice, Throwable> skippedRecipients = new HashMap<>(); 367 368 OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> builder; 369 try { 370 builder = new OmemoMessageBuilder<>( 371 userDevice, manager.getTrustCallback(), getOmemoRatchet(managerGuard.get()), messageKey, iv, message); 372 } catch (BadPaddingException | IllegalBlockSizeException | 373 NoSuchPaddingException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException e) { 374 throw new CryptoFailedException(e); 375 } 376 377 for (OmemoDevice contactsDevice : contactsDevices) { 378 // Build missing sessions 379 if (!hasSession(userDevice, contactsDevice)) { 380 try { 381 buildFreshSessionWithDevice(manager.getConnection(), userDevice, contactsDevice); 382 } catch (CorruptedOmemoKeyException | CannotEstablishOmemoSessionException e) { 383 LOGGER.log(Level.WARNING, "Could not build session with " + contactsDevice + ".", e); 384 skippedRecipients.put(contactsDevice, e); 385 continue; 386 } 387 } 388 389 int messageCounter = omemoStore.loadOmemoMessageCounter(userDevice, contactsDevice); 390 391 // Ignore read-only devices 392 if (OmemoConfiguration.getIgnoreReadOnlyDevices()) { 393 394 boolean readOnly = messageCounter >= OmemoConfiguration.getMaxReadOnlyMessageCount(); 395 396 if (readOnly) { 397 LOGGER.log(Level.FINE, "Device " + contactsDevice + " seems to be read-only (We sent " 398 + messageCounter + " messages without getting a reply back (max allowed is " + 399 OmemoConfiguration.getMaxReadOnlyMessageCount() + "). Ignoring the device."); 400 skippedRecipients.put(contactsDevice, new ReadOnlyDeviceException(contactsDevice)); 401 402 // Skip this device and handle next device 403 continue; 404 } 405 } 406 407 // Add recipients 408 try { 409 builder.addRecipient(contactsDevice); 410 } 411 catch (NoIdentityKeyException | CorruptedOmemoKeyException e) { 412 LOGGER.log(Level.WARNING, "Encryption failed for device " + contactsDevice + ".", e); 413 skippedRecipients.put(contactsDevice, e); 414 } 415 catch (UndecidedOmemoIdentityException e) { 416 throw new AssertionError("Recipients device seems to be undecided, even though we should have thrown" + 417 " an exception earlier in that case. " + e); 418 } 419 catch (UntrustedOmemoIdentityException e) { 420 LOGGER.log(Level.WARNING, "Device " + contactsDevice + " is untrusted. Message is not encrypted for it."); 421 skippedRecipients.put(contactsDevice, e); 422 } 423 424 // Increment the message counter of the device 425 omemoStore.storeOmemoMessageCounter(userDevice, contactsDevice, 426 messageCounter + 1); 427 } 428 429 OmemoElement element = builder.finish(); 430 431 return new OmemoMessage.Sent(element, messageKey, iv, contactsDevices, skippedRecipients); 432 } 433 434 /** 435 * Decrypt an OMEMO message. 436 * 437 * @param managerGuard authenticated OmemoManager. 438 * @param senderJid BareJid of the sender. 439 * @param omemoElement omemoElement. 440 * @return decrypted OmemoMessage object. 441 * 442 * @throws CorruptedOmemoKeyException if the identityKey of the sender is damaged. 443 * @throws CryptoFailedException if decryption fails. 444 * @throws NoRawSessionException if we have no session with the device and it sent a normal (non-preKey) message. 445 * @throws IOException if an I/O error occurred. 446 */ 447 OmemoMessage.Received decryptMessage(OmemoManager.LoggedInOmemoManager managerGuard, 448 BareJid senderJid, 449 OmemoElement omemoElement) 450 throws CorruptedOmemoKeyException, CryptoFailedException, NoRawSessionException, IOException { 451 452 OmemoManager manager = managerGuard.get(); 453 int senderId = omemoElement.getHeader().getSid(); 454 OmemoDevice senderDevice = new OmemoDevice(senderJid, senderId); 455 456 CipherAndAuthTag cipherAndAuthTag = getOmemoRatchet(manager) 457 .retrieveMessageKeyAndAuthTag(senderDevice, omemoElement); 458 459 // Retrieve senders fingerprint. 460 OmemoFingerprint senderFingerprint; 461 try { 462 senderFingerprint = getOmemoStoreBackend().getFingerprint(manager.getOwnDevice(), senderDevice); 463 } catch (NoIdentityKeyException e) { 464 throw new AssertionError("Cannot retrieve OmemoFingerprint of sender although decryption was successful: " + e); 465 } 466 467 // Reset the message counter. 468 omemoStore.storeOmemoMessageCounter(manager.getOwnDevice(), senderDevice, 0); 469 470 if (omemoElement.isMessageElement()) { 471 // Use symmetric message key to decrypt message payload. 472 String plaintext = OmemoRatchet.decryptMessageElement(omemoElement, cipherAndAuthTag); 473 474 return new OmemoMessage.Received(omemoElement, cipherAndAuthTag.getKey(), cipherAndAuthTag.getIv(), 475 plaintext, senderFingerprint, senderDevice, cipherAndAuthTag.wasPreKeyEncrypted()); 476 477 } else { 478 // KeyTransportMessages don't require decryption of the payload. 479 return new OmemoMessage.Received(omemoElement, cipherAndAuthTag.getKey(), cipherAndAuthTag.getIv(), 480 null, senderFingerprint, senderDevice, cipherAndAuthTag.wasPreKeyEncrypted()); 481 } 482 } 483 484 /** 485 * Create an OMEMO KeyTransportElement. 486 * 487 * @see <a href="https://xmpp.org/extensions/xep-0384.html#usecases-keysend">XEP-0384: Sending a key</a>. 488 * 489 * @param managerGuard Initialized OmemoManager. 490 * @param contactsDevices set of recipient devices. 491 * @param key AES-Key to be transported. 492 * @param iv initialization vector to be used with the key. 493 * 494 * @return a new key transport element 495 * 496 * @throws InterruptedException if the calling thread was interrupted. 497 * @throws UndecidedOmemoIdentityException if the list of recipients contains an undecided device 498 * @throws CryptoFailedException if we are lacking some cryptographic algorithms 499 * @throws SmackException.NotConnectedException if the XMPP connection is not connected. 500 * @throws SmackException.NoResponseException if there was no response from the remote entity. 501 * @throws IOException if an I/O error occurred. 502 */ 503 OmemoMessage.Sent createKeyTransportElement(OmemoManager.LoggedInOmemoManager managerGuard, 504 Set<OmemoDevice> contactsDevices, 505 byte[] key, 506 byte[] iv) 507 throws InterruptedException, UndecidedOmemoIdentityException, CryptoFailedException, 508 SmackException.NotConnectedException, SmackException.NoResponseException, IOException { 509 return encrypt(managerGuard, contactsDevices, key, iv, null); 510 } 511 512 /** 513 * Create an OmemoMessage. 514 * 515 * @param managerGuard initialized OmemoManager 516 * @param contactsDevices set of recipient devices 517 * @param message message we want to send 518 * @return encrypted OmemoMessage 519 * 520 * @throws InterruptedException if the calling thread was interrupted. 521 * @throws UndecidedOmemoIdentityException if the list of recipient devices contains an undecided device. 522 * @throws CryptoFailedException if we are lacking some cryptographic algorithms 523 * @throws SmackException.NotConnectedException if the XMPP connection is not connected. 524 * @throws SmackException.NoResponseException if there was no response from the remote entity. 525 * @throws IOException if an I/O error occurred. 526 */ 527 OmemoMessage.Sent createOmemoMessage(OmemoManager.LoggedInOmemoManager managerGuard, 528 Set<OmemoDevice> contactsDevices, 529 String message) 530 throws InterruptedException, UndecidedOmemoIdentityException, CryptoFailedException, 531 SmackException.NotConnectedException, SmackException.NoResponseException, IOException { 532 533 byte[] key, iv; 534 iv = OmemoMessageBuilder.generateIv(); 535 536 try { 537 key = OmemoMessageBuilder.generateKey(KEYTYPE, KEYLENGTH); 538 } catch (NoSuchAlgorithmException e) { 539 throw new CryptoFailedException(e); 540 } 541 542 return encrypt(managerGuard, contactsDevices, key, iv, message); 543 } 544 545 /** 546 * Retrieve a users OMEMO bundle. 547 * 548 * @param connection authenticated XMPP connection. 549 * @param contactsDevice device of which we want to retrieve the bundle. 550 * @return OmemoBundle of the device or null, if it doesn't exist. 551 * 552 * @throws SmackException.NotConnectedException if the XMPP connection is not connected. 553 * @throws InterruptedException if the calling thread was interrupted. 554 * @throws SmackException.NoResponseException if there was no response from the remote entity. 555 * @throws XMPPException.XMPPErrorException if there was an XMPP error returned. 556 * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node. 557 * @throws PubSubException.NotAPubSubNodeException if a involved node is not a PubSub node. 558 */ 559 private static OmemoBundleElement fetchBundle(XMPPConnection connection, 560 OmemoDevice contactsDevice) 561 throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, 562 XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException, 563 PubSubException.NotAPubSubNodeException { 564 565 PubSubManager pm = PubSubManager.getInstanceFor(connection, contactsDevice.getJid()); 566 LeafNode node = pm.getLeafNode(contactsDevice.getBundleNodeName()); 567 568 if (node == null) { 569 return null; 570 } 571 572 List<PayloadItem<OmemoBundleElement>> bundleItems = node.getItems(); 573 if (bundleItems.isEmpty()) { 574 return null; 575 } 576 577 return bundleItems.get(bundleItems.size() - 1).getPayload(); 578 } 579 580 /** 581 * Publish the given OMEMO bundle to the server using PubSub. 582 * 583 * @param connection our connection. 584 * @param userDevice our device 585 * @param bundle the bundle we want to publish 586 * 587 * @throws XMPPException.XMPPErrorException if there was an XMPP error returned. 588 * @throws SmackException.NotConnectedException if the XMPP connection is not connected. 589 * @throws InterruptedException if the calling thread was interrupted. 590 * @throws SmackException.NoResponseException if there was no response from the remote entity. 591 * @throws NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node. 592 */ 593 static void publishBundle(XMPPConnection connection, OmemoDevice userDevice, OmemoBundleElement bundle) 594 throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, 595 SmackException.NoResponseException, NotALeafNodeException { 596 PepManager pm = PepManager.getInstanceFor(connection); 597 pm.publish(userDevice.getBundleNodeName(), new PayloadItem<>(bundle)); 598 } 599 600 /** 601 * Retrieve the OMEMO device list of a contact. 602 * 603 * @param connection authenticated XMPP connection. 604 * @param contact BareJid of the contact of which we want to retrieve the device list from. 605 * @return device list 606 * 607 * @throws InterruptedException if the calling thread was interrupted. 608 * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node. 609 * @throws SmackException.NoResponseException if there was no response from the remote entity. 610 * @throws SmackException.NotConnectedException if the XMPP connection is not connected. 611 * @throws XMPPException.XMPPErrorException if there was an XMPP error returned. 612 * @throws PubSubException.NotAPubSubNodeException if a involved node is not a PubSub node. 613 */ 614 private static OmemoDeviceListElement fetchDeviceList(XMPPConnection connection, BareJid contact) 615 throws InterruptedException, PubSubException.NotALeafNodeException, SmackException.NoResponseException, 616 SmackException.NotConnectedException, XMPPException.XMPPErrorException, 617 PubSubException.NotAPubSubNodeException { 618 619 PubSubManager pm = PubSubManager.getInstanceFor(connection, contact); 620 String nodeName = OmemoConstants.PEP_NODE_DEVICE_LIST; 621 LeafNode node = pm.getLeafNode(nodeName); 622 623 if (node == null) { 624 return null; 625 } 626 627 List<PayloadItem<OmemoDeviceListElement>> items = node.getItems(); 628 if (items.isEmpty()) { 629 return null; 630 } 631 632 return items.get(items.size() - 1).getPayload(); 633 } 634 635 /** 636 * Publish the given device list to the server. 637 * 638 * @param connection authenticated XMPP connection. 639 * @param deviceList users deviceList. 640 * 641 * @throws InterruptedException if the calling thread was interrupted. 642 * @throws XMPPException.XMPPErrorException if there was an XMPP error returned. 643 * @throws SmackException.NotConnectedException if the XMPP connection is not connected. 644 * @throws SmackException.NoResponseException if there was no response from the remote entity. 645 * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node. 646 */ 647 static void publishDeviceList(XMPPConnection connection, OmemoDeviceListElement deviceList) 648 throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, 649 SmackException.NoResponseException, NotALeafNodeException { 650 PepManager pm = PepManager.getInstanceFor(connection); 651 pm.publish(OmemoConstants.PEP_NODE_DEVICE_LIST, new PayloadItem<>(deviceList)); 652 } 653 654 /** 655 * Refresh our own device list and publish it to the server. 656 * 657 * @param connection XMPPConnection 658 * @param userDevice our OMEMO device 659 * 660 * @throws InterruptedException if the calling thread was interrupted. 661 * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node. 662 * @throws XMPPException.XMPPErrorException if there was an XMPP error returned. 663 * @throws SmackException.NotConnectedException if the XMPP connection is not connected. 664 * @throws SmackException.NoResponseException if there was no response from the remote entity. 665 * @throws IOException if an I/O error occurred. 666 */ 667 private void refreshAndRepublishDeviceList(XMPPConnection connection, OmemoDevice userDevice) 668 throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, 669 SmackException.NotConnectedException, SmackException.NoResponseException, IOException { 670 671 // refreshOmemoDeviceList; 672 OmemoDeviceListElement publishedList; 673 674 try { 675 publishedList = fetchDeviceList(connection, userDevice.getJid()); 676 } catch (PubSubException.NotAPubSubNodeException e) { 677 // Node is not a PubSub node. This might happen on some ejabberd servers. 678 publishedList = null; 679 } catch (XMPPException.XMPPErrorException e) { 680 if (e.getStanzaError().getCondition() == StanzaError.Condition.item_not_found) { 681 // Items not found -> items do not exist 682 publishedList = null; 683 } else { 684 // Some other error -> throw 685 throw e; 686 } 687 } 688 if (publishedList == null) { 689 publishedList = new OmemoDeviceListElement_VAxolotl(Collections.<Integer>emptySet()); 690 } 691 692 getOmemoStoreBackend().mergeCachedDeviceList(userDevice, userDevice.getJid(), publishedList); 693 694 OmemoCachedDeviceList cachedList = cleanUpDeviceList(userDevice); 695 696 // Republish our deviceId if it is missing from the published list. 697 if (!publishedList.getDeviceIds().equals(cachedList.getActiveDevices())) { 698 publishDeviceList(connection, new OmemoDeviceListElement_VAxolotl(cachedList)); 699 } 700 } 701 702 /** 703 * Add our load the deviceList of the user from cache, delete stale devices if needed, add the users device 704 * back if necessary, store the refurbished list in cache and return it. 705 * 706 * @param userDevice our own OMEMO device 707 * @return cleaned device list 708 * 709 * @throws IOException if an I/O error occurred. 710 */ 711 OmemoCachedDeviceList cleanUpDeviceList(OmemoDevice userDevice) throws IOException { 712 OmemoCachedDeviceList cachedDeviceList; 713 714 // Delete stale devices if allowed and necessary 715 if (OmemoConfiguration.getDeleteStaleDevices()) { 716 cachedDeviceList = deleteStaleDevices(userDevice); 717 } else { 718 cachedDeviceList = getOmemoStoreBackend().loadCachedDeviceList(userDevice); 719 } 720 721 722 // Add back our device if necessary 723 if (!cachedDeviceList.getActiveDevices().contains(userDevice.getDeviceId())) { 724 cachedDeviceList.addDevice(userDevice.getDeviceId()); 725 } 726 727 getOmemoStoreBackend().storeCachedDeviceList(userDevice, userDevice.getJid(), cachedDeviceList); 728 return cachedDeviceList; 729 } 730 731 /** 732 * Refresh and merge device list of contact. 733 * 734 * @param connection authenticated XMPP connection 735 * @param userDevice our OmemoDevice 736 * @param contact contact we want to fetch the deviceList from 737 * @return cached device list after refresh. 738 * 739 * @throws InterruptedException if the calling thread was interrupted. 740 * @throws PubSubException.NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node. 741 * @throws XMPPException.XMPPErrorException if there was an XMPP error returned. 742 * @throws SmackException.NotConnectedException if the XMPP connection is not connected. 743 * @throws SmackException.NoResponseException if there was no response from the remote entity. 744 * @throws IOException if an I/O error occurred. 745 */ 746 OmemoCachedDeviceList refreshDeviceList(XMPPConnection connection, OmemoDevice userDevice, BareJid contact) 747 throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, 748 SmackException.NotConnectedException, SmackException.NoResponseException, IOException { 749 // refreshOmemoDeviceList; 750 OmemoDeviceListElement publishedList; 751 try { 752 publishedList = fetchDeviceList(connection, contact); 753 } catch (PubSubException.NotAPubSubNodeException e) { 754 LOGGER.log(Level.WARNING, "Error refreshing deviceList: ", e); 755 publishedList = null; 756 } 757 if (publishedList == null) { 758 publishedList = new OmemoDeviceListElement_VAxolotl(Collections.<Integer>emptySet()); 759 } 760 761 return getOmemoStoreBackend().mergeCachedDeviceList( 762 userDevice, contact, publishedList); 763 } 764 765 /** 766 * Fetch the bundle of a contact and build a fresh OMEMO session with the contacts device. 767 * Note that this builds a fresh session, regardless if we have had a session before or not. 768 * 769 * @param connection authenticated XMPP connection 770 * @param userDevice our OmemoDevice 771 * @param contactsDevice OmemoDevice of a contact. 772 * 773 * @throws CannotEstablishOmemoSessionException if we cannot establish a session (because of missing bundle etc.) 774 * @throws SmackException.NotConnectedException if the XMPP connection is not connected. 775 * @throws InterruptedException if the calling thread was interrupted. 776 * @throws SmackException.NoResponseException if there was no response from the remote entity. 777 * @throws CorruptedOmemoKeyException if our IdentityKeyPair is corrupted. 778 */ 779 void buildFreshSessionWithDevice(XMPPConnection connection, OmemoDevice userDevice, OmemoDevice contactsDevice) 780 throws CannotEstablishOmemoSessionException, SmackException.NotConnectedException, InterruptedException, 781 SmackException.NoResponseException, CorruptedOmemoKeyException { 782 783 if (contactsDevice.equals(userDevice)) { 784 // Do not build a session with yourself. 785 return; 786 } 787 788 OmemoBundleElement bundleElement; 789 try { 790 bundleElement = fetchBundle(connection, contactsDevice); 791 } catch (XMPPException.XMPPErrorException | PubSubException.NotALeafNodeException | 792 PubSubException.NotAPubSubNodeException e) { 793 throw new CannotEstablishOmemoSessionException(contactsDevice, e); 794 } 795 796 // Select random Bundle 797 HashMap<Integer, T_Bundle> bundlesList = getOmemoStoreBackend().keyUtil().BUNDLE.bundles(bundleElement, contactsDevice); 798 int randomIndex = new Random().nextInt(bundlesList.size()); 799 T_Bundle randomPreKeyBundle = new ArrayList<>(bundlesList.values()).get(randomIndex); 800 801 // build the session 802 OmemoManager omemoManager = OmemoManager.getInstanceFor(connection, userDevice.getDeviceId()); 803 processBundle(omemoManager, randomPreKeyBundle, contactsDevice); 804 } 805 806 /** 807 * Build sessions with all devices from the set, we don't have a session with yet. 808 * Return the set of all devices we have a session with afterwards. 809 * 810 * @param connection authenticated XMPP connection 811 * @param userDevice our OmemoDevice 812 * @param devices set of devices we may want to build a session with if necessary 813 * @return set of all devices with sessions 814 * 815 * @throws SmackException.NotConnectedException if the XMPP connection is not connected. 816 * @throws InterruptedException if the calling thread was interrupted. 817 * @throws SmackException.NoResponseException if there was no response from the remote entity. 818 * @throws IOException if an I/O error occurred. 819 */ 820 private Set<OmemoDevice> buildMissingSessionsWithDevices(XMPPConnection connection, 821 OmemoDevice userDevice, 822 Set<OmemoDevice> devices) 823 throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, IOException { 824 825 Set<OmemoDevice> devicesWithSession = new HashSet<>(); 826 for (OmemoDevice device : devices) { 827 828 if (hasSession(userDevice, device)) { 829 devicesWithSession.add(device); 830 continue; 831 } 832 833 try { 834 buildFreshSessionWithDevice(connection, userDevice, device); 835 devicesWithSession.add(device); 836 } catch (CannotEstablishOmemoSessionException e) { 837 LOGGER.log(Level.WARNING, userDevice + " cannot establish session with " + device + 838 " because their bundle could not be fetched.", e); 839 } catch (CorruptedOmemoKeyException e) { 840 LOGGER.log(Level.WARNING, userDevice + " could not establish session with " + device + 841 "because their bundle seems to be corrupt.", e); 842 } 843 844 } 845 846 return devicesWithSession; 847 } 848 849 /** 850 * Return a set of all devices from the provided set, which trust level is undecided. 851 * A device is also considered undecided, if its fingerprint cannot be loaded. 852 * 853 * @param userDevice our OmemoDevice 854 * @param callback OmemoTrustCallback to query the trust decisions from 855 * @param devices set of OmemoDevices 856 * @return set of OmemoDevices which contains all devices from the set devices, which are undecided 857 * 858 * @throws IOException if an I/O error occurred. 859 */ 860 private Set<OmemoDevice> getUndecidedDevices(OmemoDevice userDevice, OmemoTrustCallback callback, Set<OmemoDevice> devices) throws IOException { 861 Set<OmemoDevice> undecidedDevices = new HashSet<>(); 862 863 for (OmemoDevice device : devices) { 864 865 OmemoFingerprint fingerprint; 866 try { 867 fingerprint = getOmemoStoreBackend().getFingerprint(userDevice, device); 868 } catch (CorruptedOmemoKeyException | NoIdentityKeyException e) { 869 LOGGER.log(Level.WARNING, "Could not load fingerprint of " + device, e); 870 undecidedDevices.add(device); 871 continue; 872 } 873 874 if (callback.getTrust(device, fingerprint) == TrustState.undecided) { 875 undecidedDevices.add(device); 876 } 877 } 878 879 return undecidedDevices; 880 } 881 882 /** 883 * Return true, if the OmemoManager of userDevice has a session with the contactsDevice. 884 * 885 * @param userDevice our OmemoDevice. 886 * @param contactsDevice OmemoDevice of the contact. 887 * @return true if userDevice has session with contactsDevice. 888 * 889 * @throws IOException if an I/O error occurred. 890 */ 891 private boolean hasSession(OmemoDevice userDevice, OmemoDevice contactsDevice) throws IOException { 892 return getOmemoStoreBackend().loadRawSession(userDevice, contactsDevice) != null; 893 } 894 895 /** 896 * Process a received bundle. Typically that includes saving keys and building a session. 897 * 898 * @param omemoManager our OmemoManager 899 * @param contactsBundle bundle of the contact 900 * @param contactsDevice OmemoDevice of the contact 901 * 902 * @throws CorruptedOmemoKeyException if the OMEMO key is corrupted. 903 */ 904 protected abstract void processBundle(OmemoManager omemoManager, 905 T_Bundle contactsBundle, 906 OmemoDevice contactsDevice) 907 throws CorruptedOmemoKeyException; 908 909 /** 910 * Returns true, if a rotation of the signed preKey is necessary. 911 * 912 * @param userDevice our OmemoDevice 913 * @return true if rotation is necessary 914 * 915 * @throws IOException if an I/O error occurred. 916 */ 917 private boolean shouldRotateSignedPreKey(OmemoDevice userDevice) throws IOException { 918 if (!OmemoConfiguration.getRenewOldSignedPreKeys()) { 919 return false; 920 } 921 922 Date now = new Date(); 923 Date lastRenewal = getOmemoStoreBackend().getDateOfLastSignedPreKeyRenewal(userDevice); 924 925 if (lastRenewal == null) { 926 lastRenewal = new Date(); 927 getOmemoStoreBackend().setDateOfLastSignedPreKeyRenewal(userDevice, lastRenewal); 928 } 929 930 long allowedAgeMillis = MILLIS_PER_HOUR * OmemoConfiguration.getRenewOldSignedPreKeysAfterHours(); 931 return now.getTime() - lastRenewal.getTime() > allowedAgeMillis; 932 } 933 934 /** 935 * Return a copy of our deviceList, but with stale devices marked as inactive. 936 * Never mark our own device as stale. 937 * This method ignores {@link OmemoConfiguration#getDeleteStaleDevices()}! 938 * 939 * In this case, a stale device is one of our devices, from which we haven't received an OMEMO message from 940 * for more than {@link OmemoConfiguration#getDeleteStaleDevicesAfterHours()} hours. 941 * 942 * @param userDevice our OmemoDevice 943 * @return our altered deviceList with stale devices marked as inactive. 944 * 945 * @throws IOException if an I/O error occurred. 946 */ 947 private OmemoCachedDeviceList deleteStaleDevices(OmemoDevice userDevice) throws IOException { 948 OmemoCachedDeviceList deviceList = getOmemoStoreBackend().loadCachedDeviceList(userDevice); 949 int maxAgeHours = OmemoConfiguration.getDeleteStaleDevicesAfterHours(); 950 return removeStaleDevicesFromDeviceList(userDevice, userDevice.getJid(), deviceList, maxAgeHours); 951 } 952 953 /** 954 * Return a copy of the given deviceList of user contact, but with stale devices marked as inactive. 955 * Never mark our own device as stale. If we haven't yet received a message from a device, store the current date 956 * as last date of message receipt to allow future decisions. 957 * 958 * A stale device is a device, from which we haven't received an OMEMO message from for more than 959 * "maxAgeMillis" milliseconds. 960 * 961 * @param userDevice our OmemoDevice. 962 * @param contact subjects BareJid. 963 * @param contactsDeviceList subjects deviceList. 964 * @return copy of subjects deviceList with stale devices marked as inactive. 965 * 966 * @throws IOException if an I/O error occurred. 967 */ 968 private OmemoCachedDeviceList removeStaleDevicesFromDeviceList(OmemoDevice userDevice, 969 BareJid contact, 970 OmemoCachedDeviceList contactsDeviceList, 971 int maxAgeHours) throws IOException { 972 OmemoCachedDeviceList deviceList = new OmemoCachedDeviceList(contactsDeviceList); // Don't work on original list. 973 974 // Iterate through original list, but modify copy instead 975 for (int deviceId : contactsDeviceList.getActiveDevices()) { 976 OmemoDevice device = new OmemoDevice(contact, deviceId); 977 978 Date lastDeviceIdPublication = getOmemoStoreBackend().getDateOfLastDeviceIdPublication(userDevice, device); 979 if (lastDeviceIdPublication == null) { 980 lastDeviceIdPublication = new Date(); 981 getOmemoStoreBackend().setDateOfLastDeviceIdPublication(userDevice, device, lastDeviceIdPublication); 982 } 983 984 Date lastMessageReceived = getOmemoStoreBackend().getDateOfLastReceivedMessage(userDevice, device); 985 if (lastMessageReceived == null) { 986 lastMessageReceived = new Date(); 987 getOmemoStoreBackend().setDateOfLastReceivedMessage(userDevice, device, lastMessageReceived); 988 } 989 990 boolean stale = isStale(userDevice, device, lastDeviceIdPublication, maxAgeHours); 991 stale &= isStale(userDevice, device, lastMessageReceived, maxAgeHours); 992 993 if (stale) { 994 deviceList.addInactiveDevice(deviceId); 995 } 996 } 997 return deviceList; 998 } 999 1000 1001 /** 1002 * Remove our device from the collection of devices. 1003 * 1004 * @param userDevice our OmemoDevice 1005 * @param devices collection of OmemoDevices 1006 */ 1007 static void removeOurDevice(OmemoDevice userDevice, Collection<OmemoDevice> devices) { 1008 if (devices.contains(userDevice)) { 1009 devices.remove(userDevice); 1010 } 1011 } 1012 1013 /** 1014 * Determine, whether another one of *our* devices is stale or not. 1015 * 1016 * @param userDevice our omemoDevice 1017 * @param subject another one of our devices 1018 * @param lastReceipt date of last received message from that device 1019 * @param maxAgeHours threshold 1020 * 1021 * @return true if the subject device is considered stale 1022 */ 1023 static boolean isStale(OmemoDevice userDevice, OmemoDevice subject, Date lastReceipt, int maxAgeHours) { 1024 if (userDevice.equals(subject)) { 1025 return false; 1026 } 1027 1028 if (lastReceipt == null) { 1029 return false; 1030 } 1031 1032 long maxAgeMillis = MILLIS_PER_HOUR * maxAgeHours; 1033 Date now = new Date(); 1034 1035 return now.getTime() - lastReceipt.getTime() > maxAgeMillis; 1036 } 1037 1038 /** 1039 * Gullible TrustCallback, which returns all queried identities as trusted. 1040 * This is only used for insensitive OMEMO messages like RatchetUpdateMessages. 1041 * DO NOT USE THIS FOR ANYTHING ELSE! 1042 */ 1043 private static final OmemoTrustCallback gullibleTrustCallback = new OmemoTrustCallback() { 1044 @Override 1045 public TrustState getTrust(OmemoDevice device, OmemoFingerprint fingerprint) { 1046 return TrustState.trusted; 1047 } 1048 1049 @Override 1050 public void setTrust(OmemoDevice device, OmemoFingerprint fingerprint, TrustState state) { 1051 // Not needed 1052 } 1053 }; 1054 1055 /** 1056 * Decrypt a possible OMEMO encrypted messages in a {@link MamManager.MamQuery}. 1057 * The returned list contains wrappers that either hold an {@link OmemoMessage} in case the message was decrypted 1058 * properly, otherwise it contains the message itself. 1059 * 1060 * @param managerGuard authenticated OmemoManager. 1061 * @param mamQuery Mam archive query 1062 * @return list of {@link MessageOrOmemoMessage MessageOrOmemoMessages}. 1063 * 1064 * @throws IOException if an I/O error occurred. 1065 */ 1066 List<MessageOrOmemoMessage> decryptMamQueryResult(OmemoManager.LoggedInOmemoManager managerGuard, 1067 MamManager.MamQuery mamQuery) throws IOException { 1068 List<MessageOrOmemoMessage> result = new ArrayList<>(); 1069 for (Message message : mamQuery.getMessages()) { 1070 if (OmemoManager.stanzaContainsOmemoElement(message)) { 1071 OmemoElement element = 1072 (OmemoElement) message.getExtensionElement(OmemoElement.NAME_ENCRYPTED, OmemoConstants.OMEMO_NAMESPACE_V_AXOLOTL); 1073 // Decrypt OMEMO messages 1074 try { 1075 OmemoMessage.Received omemoMessage = decryptMessage(managerGuard, message.getFrom().asBareJid(), element); 1076 result.add(new MessageOrOmemoMessage(omemoMessage)); 1077 } catch (NoRawSessionException | CorruptedOmemoKeyException | CryptoFailedException e) { 1078 LOGGER.log(Level.WARNING, "decryptMamQueryResult failed to decrypt message from " 1079 + message.getFrom() + " due to corrupted session/key: " + e.getMessage()); 1080 result.add(new MessageOrOmemoMessage(message)); 1081 } 1082 } else { 1083 // Wrap cleartext messages 1084 result.add(new MessageOrOmemoMessage(message)); 1085 } 1086 } 1087 1088 return result; 1089 } 1090 1091 1092 @Override 1093 public void onOmemoCarbonCopyReceived(CarbonExtension.Direction direction, 1094 Message carbonCopy, 1095 Message wrappingMessage, 1096 OmemoManager.LoggedInOmemoManager managerGuard) throws IOException { 1097 OmemoManager manager = managerGuard.get(); 1098 // Avoid the ratchet being manipulated and the bundle being published multiple times simultaneously 1099 synchronized (manager) { 1100 OmemoDevice userDevice = manager.getOwnDevice(); 1101 OmemoElement element = (OmemoElement) carbonCopy.getExtensionElement(OmemoElement.NAME_ENCRYPTED, OmemoElement_VAxolotl.NAMESPACE); 1102 if (element == null) { 1103 return; 1104 } 1105 1106 OmemoMessage.Received decrypted; 1107 BareJid sender = carbonCopy.getFrom().asBareJid(); 1108 1109 try { 1110 decrypted = decryptMessage(managerGuard, sender, element); 1111 manager.notifyOmemoCarbonCopyReceived(direction, carbonCopy, wrappingMessage, decrypted); 1112 1113 if (decrypted.isPreKeyMessage() && OmemoConfiguration.getCompleteSessionWithEmptyMessage()) { 1114 LOGGER.log(Level.FINE, "Received a preKeyMessage in a carbon copy from " + decrypted.getSenderDevice() + ".\n" + 1115 "Complete the session by sending an empty response message."); 1116 try { 1117 sendRatchetUpdate(managerGuard, decrypted.getSenderDevice()); 1118 } catch (CannotEstablishOmemoSessionException e) { 1119 throw new AssertionError("Since we successfully received a message, we MUST be able to " + 1120 "establish a session. " + e); 1121 } catch (NoSuchAlgorithmException | InterruptedException | SmackException.NotConnectedException | SmackException.NoResponseException e) { 1122 LOGGER.log(Level.WARNING, "Cannot send a ratchet update message.", e); 1123 } 1124 } 1125 } catch (NoRawSessionException e) { 1126 OmemoDevice device = e.getDeviceWithoutSession(); 1127 LOGGER.log(Level.WARNING, "No raw session found for contact " + device + ". ", e); 1128 1129 if (OmemoConfiguration.getRepairBrokenSessionsWithPreKeyMessages()) { 1130 repairBrokenSessionWithPreKeyMessage(managerGuard, device); 1131 } 1132 } catch (CorruptedOmemoKeyException | CryptoFailedException e) { 1133 LOGGER.log(Level.WARNING, "Could not decrypt incoming carbon copy: ", e); 1134 } 1135 1136 // Upload fresh bundle. 1137 if (getOmemoStoreBackend().loadOmemoPreKeys(userDevice).size() < OmemoConstants.PRE_KEY_COUNT_PER_BUNDLE) { 1138 LOGGER.log(Level.FINE, "We used up a preKey. Upload a fresh bundle."); 1139 try { 1140 getOmemoStoreBackend().replenishKeys(userDevice); 1141 OmemoBundleElement bundleElement = getOmemoStoreBackend().packOmemoBundle(userDevice); 1142 publishBundle(manager.getConnection(), userDevice, bundleElement); 1143 } catch (CorruptedOmemoKeyException | InterruptedException | SmackException.NoResponseException 1144 | SmackException.NotConnectedException | XMPPException.XMPPErrorException 1145 | NotALeafNodeException e) { 1146 LOGGER.log(Level.WARNING, "Could not republish replenished bundle.", e); 1147 } 1148 } 1149 } 1150 } 1151 1152 @Override 1153 public void onOmemoMessageStanzaReceived(Stanza stanza, OmemoManager.LoggedInOmemoManager managerGuard) throws IOException { 1154 OmemoManager manager = managerGuard.get(); 1155 // Avoid the ratchet being manipulated and the bundle being published multiple times simultaneously 1156 synchronized (manager) { 1157 OmemoDevice userDevice = manager.getOwnDevice(); 1158 OmemoElement element = (OmemoElement) stanza.getExtensionElement(OmemoElement.NAME_ENCRYPTED, OmemoElement_VAxolotl.NAMESPACE); 1159 if (element == null) { 1160 return; 1161 } 1162 1163 OmemoMessage.Received decrypted; 1164 BareJid sender; 1165 1166 try { 1167 MultiUserChat muc = getMuc(manager.getConnection(), stanza.getFrom()); 1168 if (muc != null) { 1169 Occupant occupant = muc.getOccupant(stanza.getFrom().asEntityFullJidIfPossible()); 1170 if (occupant == null) { 1171 LOGGER.log(Level.WARNING, "Cannot decrypt OMEMO MUC message; MUC Occupant is null."); 1172 return; 1173 } 1174 Jid occupantJid = occupant.getJid(); 1175 1176 if (occupantJid == null) { 1177 LOGGER.log(Level.WARNING, "Cannot decrypt OMEMO MUC message; Senders Jid is null. " + 1178 stanza.getFrom()); 1179 return; 1180 } 1181 1182 sender = occupantJid.asBareJid(); 1183 1184 // try is for this 1185 decrypted = decryptMessage(managerGuard, sender, element); 1186 manager.notifyOmemoMucMessageReceived(muc, stanza, decrypted); 1187 1188 } else { 1189 sender = stanza.getFrom().asBareJid(); 1190 1191 // and this 1192 decrypted = decryptMessage(managerGuard, sender, element); 1193 manager.notifyOmemoMessageReceived(stanza, decrypted); 1194 } 1195 1196 if (decrypted.isPreKeyMessage() && OmemoConfiguration.getCompleteSessionWithEmptyMessage()) { 1197 LOGGER.log(Level.FINE, "Received a preKeyMessage from " + decrypted.getSenderDevice() + ".\n" + 1198 "Complete the session by sending an empty response message."); 1199 try { 1200 sendRatchetUpdate(managerGuard, decrypted.getSenderDevice()); 1201 } catch (CannotEstablishOmemoSessionException e) { 1202 throw new AssertionError("Since we successfully received a message, we MUST be able to " + 1203 "establish a session. " + e); 1204 } catch (NoSuchAlgorithmException | InterruptedException | SmackException.NotConnectedException | SmackException.NoResponseException e) { 1205 LOGGER.log(Level.WARNING, "Cannot send a ratchet update message.", e); 1206 } 1207 } 1208 } catch (NoRawSessionException e) { 1209 OmemoDevice device = e.getDeviceWithoutSession(); 1210 LOGGER.log(Level.WARNING, "No raw session found for contact " + device + ". ", e); 1211 1212 if (OmemoConfiguration.getRepairBrokenSessionsWithPreKeyMessages()) { 1213 repairBrokenSessionWithPreKeyMessage(managerGuard, device); 1214 } 1215 } catch (CorruptedOmemoKeyException | CryptoFailedException e) { 1216 LOGGER.log(Level.WARNING, "Could not decrypt incoming message: ", e); 1217 } 1218 1219 // Upload fresh bundle. 1220 if (getOmemoStoreBackend().loadOmemoPreKeys(userDevice).size() < OmemoConstants.PRE_KEY_COUNT_PER_BUNDLE) { 1221 LOGGER.log(Level.FINE, "We used up a preKey. Upload a fresh bundle."); 1222 try { 1223 getOmemoStoreBackend().replenishKeys(userDevice); 1224 OmemoBundleElement bundleElement = getOmemoStoreBackend().packOmemoBundle(userDevice); 1225 publishBundle(manager.getConnection(), userDevice, bundleElement); 1226 } catch (CorruptedOmemoKeyException | InterruptedException | SmackException.NoResponseException 1227 | SmackException.NotConnectedException | XMPPException.XMPPErrorException 1228 | NotALeafNodeException e) { 1229 LOGGER.log(Level.WARNING, "Could not republish replenished bundle.", e); 1230 } 1231 } 1232 } 1233 } 1234 1235 /** 1236 * Decrypt the OmemoElement inside the given Stanza and return it. 1237 * Return null if something goes wrong. 1238 * 1239 * @param stanza stanza 1240 * @param managerGuard authenticated OmemoManager 1241 * @return decrypted OmemoMessage or null 1242 * 1243 * @throws IOException if an I/O error occurred. 1244 */ 1245 OmemoMessage.Received decryptStanza(Stanza stanza, OmemoManager.LoggedInOmemoManager managerGuard) throws IOException { 1246 OmemoManager manager = managerGuard.get(); 1247 // Avoid the ratchet being manipulated and the bundle being published multiple times simultaneously 1248 synchronized (manager) { 1249 OmemoDevice userDevice = manager.getOwnDevice(); 1250 OmemoElement element = (OmemoElement) stanza.getExtensionElement(OmemoElement.NAME_ENCRYPTED, OmemoElement_VAxolotl.NAMESPACE); 1251 if (element == null) { 1252 return null; 1253 } 1254 1255 OmemoMessage.Received decrypted = null; 1256 BareJid sender; 1257 1258 try { 1259 MultiUserChat muc = getMuc(manager.getConnection(), stanza.getFrom()); 1260 if (muc != null) { 1261 Occupant occupant = muc.getOccupant(stanza.getFrom().asEntityFullJidIfPossible()); 1262 Jid occupantJid = occupant.getJid(); 1263 1264 if (occupantJid == null) { 1265 LOGGER.log(Level.WARNING, "MUC message received, but there is no way to retrieve the senders Jid. " + 1266 stanza.getFrom()); 1267 return null; 1268 } 1269 1270 sender = occupantJid.asBareJid(); 1271 1272 // try is for this 1273 decrypted = decryptMessage(managerGuard, sender, element); 1274 1275 } else { 1276 sender = stanza.getFrom().asBareJid(); 1277 1278 // and this 1279 decrypted = decryptMessage(managerGuard, sender, element); 1280 } 1281 1282 if (decrypted.isPreKeyMessage() && OmemoConfiguration.getCompleteSessionWithEmptyMessage()) { 1283 LOGGER.log(Level.FINE, "Received a preKeyMessage from " + decrypted.getSenderDevice() + ".\n" + 1284 "Complete the session by sending an empty response message."); 1285 try { 1286 sendRatchetUpdate(managerGuard, decrypted.getSenderDevice()); 1287 } catch (CannotEstablishOmemoSessionException e) { 1288 throw new AssertionError("Since we successfully received a message, we MUST be able to " + 1289 "establish a session. " + e); 1290 } catch (NoSuchAlgorithmException | InterruptedException | SmackException.NotConnectedException | SmackException.NoResponseException e) { 1291 LOGGER.log(Level.WARNING, "Cannot send a ratchet update message.", e); 1292 } 1293 } 1294 } catch (NoRawSessionException e) { 1295 OmemoDevice device = e.getDeviceWithoutSession(); 1296 LOGGER.log(Level.WARNING, "No raw session found for contact " + device + ". ", e); 1297 1298 } catch (CorruptedOmemoKeyException | CryptoFailedException e) { 1299 LOGGER.log(Level.WARNING, "Could not decrypt incoming message: ", e); 1300 } 1301 1302 // Upload fresh bundle. 1303 if (getOmemoStoreBackend().loadOmemoPreKeys(userDevice).size() < OmemoConstants.PRE_KEY_COUNT_PER_BUNDLE) { 1304 LOGGER.log(Level.FINE, "We used up a preKey. Upload a fresh bundle."); 1305 try { 1306 getOmemoStoreBackend().replenishKeys(userDevice); 1307 OmemoBundleElement bundleElement = getOmemoStoreBackend().packOmemoBundle(userDevice); 1308 publishBundle(manager.getConnection(), userDevice, bundleElement); 1309 } catch (CorruptedOmemoKeyException | InterruptedException | SmackException.NoResponseException 1310 | SmackException.NotConnectedException | XMPPException.XMPPErrorException 1311 | NotALeafNodeException e) { 1312 LOGGER.log(Level.WARNING, "Could not republish replenished bundle.", e); 1313 } 1314 } 1315 return decrypted; 1316 } 1317 } 1318 1319 /** 1320 * Fetch and process a fresh bundle and send an empty preKeyMessage in order to establish a fresh session. 1321 * 1322 * @param managerGuard authenticated OmemoManager. 1323 * @param brokenDevice device which session broke. 1324 * 1325 * @throws IOException if an I/O error occurred. 1326 */ 1327 private void repairBrokenSessionWithPreKeyMessage(OmemoManager.LoggedInOmemoManager managerGuard, 1328 OmemoDevice brokenDevice) throws IOException { 1329 1330 LOGGER.log(Level.WARNING, "Attempt to repair the session by sending a fresh preKey message to " 1331 + brokenDevice); 1332 OmemoManager manager = managerGuard.get(); 1333 try { 1334 // Create fresh session and send new preKeyMessage. 1335 buildFreshSessionWithDevice(manager.getConnection(), manager.getOwnDevice(), brokenDevice); 1336 sendRatchetUpdate(managerGuard, brokenDevice); 1337 1338 } catch (CannotEstablishOmemoSessionException | CorruptedOmemoKeyException e) { 1339 LOGGER.log(Level.WARNING, "Unable to repair session with " + brokenDevice, e); 1340 } catch (SmackException.NotConnectedException | InterruptedException | SmackException.NoResponseException e) { 1341 LOGGER.log(Level.WARNING, "Could not fetch fresh bundle for " + brokenDevice, e); 1342 } catch (CryptoFailedException | NoSuchAlgorithmException e) { 1343 LOGGER.log(Level.WARNING, "Could not create PreKeyMessage", e); 1344 } 1345 } 1346 1347 /** 1348 * Send an empty OMEMO message to contactsDevice in order to forward the ratchet. 1349 * 1350 * @param managerGuard OMEMO manager 1351 * @param contactsDevice contacts OMEMO device 1352 * 1353 * @throws CorruptedOmemoKeyException if our or their OMEMO key is corrupted. 1354 * @throws InterruptedException if the calling thread was interrupted. 1355 * @throws SmackException.NoResponseException if there was no response from the remote entity. 1356 * @throws NoSuchAlgorithmException if AES encryption fails 1357 * @throws SmackException.NotConnectedException if the XMPP connection is not connected. 1358 * @throws CryptoFailedException if encryption fails (should not happen though, but who knows...) 1359 * @throws CannotEstablishOmemoSessionException if we cannot establish a session with contactsDevice. 1360 * @throws IOException if an I/O error occurred. 1361 */ 1362 private void sendRatchetUpdate(OmemoManager.LoggedInOmemoManager managerGuard, OmemoDevice contactsDevice) 1363 throws CorruptedOmemoKeyException, InterruptedException, SmackException.NoResponseException, 1364 NoSuchAlgorithmException, SmackException.NotConnectedException, CryptoFailedException, 1365 CannotEstablishOmemoSessionException, IOException { 1366 1367 OmemoManager manager = managerGuard.get(); 1368 OmemoElement ratchetUpdate = createRatchetUpdateElement(managerGuard, contactsDevice); 1369 1370 XMPPConnection connection = manager.getConnection(); 1371 Message message = connection.getStanzaFactory().buildMessageStanza() 1372 .to(contactsDevice.getJid()) 1373 .addExtension(ratchetUpdate) 1374 .build(); 1375 connection.sendStanza(message); 1376 } 1377 1378 /** 1379 * Return the joined MUC with EntityBareJid jid, or null if its not a room and/or not joined. 1380 * 1381 * @param connection xmpp connection 1382 * @param jid jid (presumably) of the MUC 1383 * @return MultiUserChat or null if not a MUC. 1384 */ 1385 private static MultiUserChat getMuc(XMPPConnection connection, Jid jid) { 1386 EntityBareJid ebj = jid.asEntityBareJidIfPossible(); 1387 if (ebj == null) { 1388 return null; 1389 } 1390 1391 MultiUserChatManager mucm = MultiUserChatManager.getInstanceFor(connection); 1392 Set<EntityBareJid> joinedRooms = mucm.getJoinedRooms(); 1393 if (joinedRooms.contains(ebj)) { 1394 return mucm.getMultiUserChat(ebj); 1395 } 1396 1397 return null; 1398 } 1399 1400 /** 1401 * Publish a new DeviceList with just our device in it. 1402 * 1403 * @param managerGuard authenticated OmemoManager. 1404 * 1405 * @throws InterruptedException if the calling thread was interrupted. 1406 * @throws XMPPException.XMPPErrorException if there was an XMPP error returned. 1407 * @throws SmackException.NotConnectedException if the XMPP connection is not connected. 1408 * @throws SmackException.NoResponseException if there was no response from the remote entity. 1409 * @throws IOException if an I/O error occurred. 1410 * @throws NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node. 1411 */ 1412 public void purgeDeviceList(OmemoManager.LoggedInOmemoManager managerGuard) 1413 throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, 1414 SmackException.NoResponseException, IOException, NotALeafNodeException { 1415 1416 OmemoManager omemoManager = managerGuard.get(); 1417 OmemoDevice userDevice = omemoManager.getOwnDevice(); 1418 1419 OmemoDeviceListElement_VAxolotl newList = 1420 new OmemoDeviceListElement_VAxolotl(Collections.singleton(userDevice.getDeviceId())); 1421 1422 // Merge list 1423 getOmemoStoreBackend().mergeCachedDeviceList(userDevice, userDevice.getJid(), newList); 1424 1425 OmemoService.publishDeviceList(omemoManager.getConnection(), newList); 1426 } 1427}