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