001/** 002 * 003 * Copyright 2017 Paul Schaub 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smackx.omemo; 018 019import static org.jivesoftware.smackx.omemo.util.OmemoConstants.OMEMO_NAMESPACE_V_AXOLOTL; 020import static org.jivesoftware.smackx.omemo.util.OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID; 021import static org.jivesoftware.smackx.omemo.util.OmemoConstants.PEP_NODE_DEVICE_LIST; 022 023import java.io.UnsupportedEncodingException; 024import java.security.InvalidAlgorithmParameterException; 025import java.security.InvalidKeyException; 026import java.security.NoSuchAlgorithmException; 027import java.security.NoSuchProviderException; 028import java.security.Security; 029import java.util.ArrayList; 030import java.util.Collections; 031import java.util.Date; 032import java.util.HashMap; 033import java.util.HashSet; 034import java.util.Iterator; 035import java.util.List; 036import java.util.Map; 037import java.util.Random; 038import java.util.Set; 039import java.util.logging.Level; 040import java.util.logging.Logger; 041import javax.crypto.BadPaddingException; 042import javax.crypto.IllegalBlockSizeException; 043import javax.crypto.NoSuchPaddingException; 044 045import org.jivesoftware.smack.SmackException; 046import org.jivesoftware.smack.StanzaListener; 047import org.jivesoftware.smack.XMPPException; 048import org.jivesoftware.smack.filter.StanzaFilter; 049import org.jivesoftware.smack.packet.Message; 050import org.jivesoftware.smack.packet.Stanza; 051import org.jivesoftware.smack.packet.XMPPError; 052import org.jivesoftware.smack.util.Async; 053 054import org.jivesoftware.smackx.carbons.CarbonCopyReceivedListener; 055import org.jivesoftware.smackx.carbons.CarbonManager; 056import org.jivesoftware.smackx.carbons.packet.CarbonExtension; 057import org.jivesoftware.smackx.forward.packet.Forwarded; 058import org.jivesoftware.smackx.mam.MamManager; 059import org.jivesoftware.smackx.muc.MultiUserChat; 060import org.jivesoftware.smackx.muc.MultiUserChatManager; 061import org.jivesoftware.smackx.omemo.element.OmemoBundleVAxolotlElement; 062import org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement; 063import org.jivesoftware.smackx.omemo.element.OmemoDeviceListVAxolotlElement; 064import org.jivesoftware.smackx.omemo.element.OmemoElement; 065import org.jivesoftware.smackx.omemo.element.OmemoVAxolotlElement; 066import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException; 067import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException; 068import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException; 069import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException; 070import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException; 071import org.jivesoftware.smackx.omemo.internal.CachedDeviceList; 072import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag; 073import org.jivesoftware.smackx.omemo.internal.ClearTextMessage; 074import org.jivesoftware.smackx.omemo.internal.IdentityKeyWrapper; 075import org.jivesoftware.smackx.omemo.internal.OmemoDevice; 076import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation; 077import org.jivesoftware.smackx.omemo.internal.OmemoSession; 078import org.jivesoftware.smackx.omemo.util.OmemoConstants; 079import org.jivesoftware.smackx.omemo.util.OmemoMessageBuilder; 080import org.jivesoftware.smackx.pubsub.LeafNode; 081import org.jivesoftware.smackx.pubsub.PayloadItem; 082import org.jivesoftware.smackx.pubsub.PubSubException; 083import org.jivesoftware.smackx.pubsub.PubSubException.NotAPubSubNodeException; 084import org.jivesoftware.smackx.pubsub.PubSubManager; 085 086import org.bouncycastle.jce.provider.BouncyCastleProvider; 087import org.jxmpp.jid.BareJid; 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 * @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 106 static { 107 Security.addProvider(new BouncyCastleProvider()); 108 } 109 110 protected static final Logger LOGGER = Logger.getLogger(OmemoService.class.getName()); 111 112 private static OmemoService<?, ?, ?, ?, ?, ?, ?, ?, ?> INSTANCE; 113 114 protected OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore; 115 116 public static OmemoService<?, ?, ?, ?, ?, ?, ?, ?, ?> getInstance() { 117 if (INSTANCE == null) { 118 throw new IllegalStateException("No OmemoService registered"); 119 } 120 return INSTANCE; 121 } 122 123 /** 124 * Set singleton instance. Throws an IllegalStateException, if there is already a service set as instance. 125 * 126 * @param omemoService instance 127 */ 128 protected static void setInstance(OmemoService<?, ?, ?, ?, ?, ?, ?, ?, ?> omemoService) { 129 if (INSTANCE != null) { 130 throw new IllegalStateException("An OmemoService is already registered"); 131 } 132 INSTANCE = omemoService; 133 } 134 135 public static boolean isServiceRegistered() { 136 return INSTANCE != null; 137 } 138 139 /** 140 * Return the used omemoStore backend. 141 * If there is no store backend set yet, set the default one (typically a file-based one). 142 * 143 * @return omemoStore backend 144 */ 145 public OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> 146 getOmemoStoreBackend() { 147 if (omemoStore == null) { 148 setOmemoStoreBackend(createDefaultOmemoStoreBackend()); 149 return getOmemoStoreBackend(); 150 } 151 return omemoStore; 152 } 153 154 /** 155 * Set an omemoStore as backend. Throws an IllegalStateException, if there is already a backend set. 156 * 157 * @param omemoStore store. 158 */ 159 public void setOmemoStoreBackend( 160 OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore) { 161 if (this.omemoStore != null) { 162 throw new IllegalStateException("An OmemoStore backend has already been set."); 163 } 164 this.omemoStore = omemoStore; 165 } 166 167 /** 168 * Create a default OmemoStore object. 169 * 170 * @return default omemoStore. 171 */ 172 public abstract OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> 173 createDefaultOmemoStoreBackend(); 174 175 /** 176 * Create a new OmemoService object. This should only happen once. 177 * When the service gets created, it tries a placeholder crypto function in order to test, if all necessary 178 * algorithms are available on the system. 179 * 180 * @throws NoSuchPaddingException When no Cipher could be instantiated. 181 * @throws NoSuchAlgorithmException when no Cipher could be instantiated. 182 * @throws NoSuchProviderException when BouncyCastle could not be found. 183 * @throws InvalidAlgorithmParameterException when the Cipher could not be initialized 184 * @throws InvalidKeyException when the generated key is invalid 185 * @throws UnsupportedEncodingException when UTF8 is unavailable 186 * @throws BadPaddingException when cipher.doFinal gets wrong padding 187 * @throws IllegalBlockSizeException when cipher.doFinal gets wrong Block size. 188 */ 189 public OmemoService() 190 throws NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException, IllegalBlockSizeException, 191 BadPaddingException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException { 192 193 // Check availability of algorithms and encodings needed for crypto 194 checkAvailableAlgorithms(); 195 } 196 197 /** 198 * Initialize OMEMO functionality for OmemoManager omemoManager. 199 * 200 * @param omemoManager OmemoManager we'd like to initialize. 201 * @throws InterruptedException 202 * @throws CorruptedOmemoKeyException 203 * @throws XMPPException.XMPPErrorException 204 * @throws SmackException.NotConnectedException 205 * @throws SmackException.NoResponseException 206 * @throws SmackException.NotLoggedInException 207 * @throws PubSubException.NotALeafNodeException 208 */ 209 void initialize(OmemoManager omemoManager) throws InterruptedException, CorruptedOmemoKeyException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException, SmackException.NotLoggedInException, PubSubException.NotALeafNodeException { 210 if (!omemoManager.getConnection().isAuthenticated()) { 211 throw new SmackException.NotLoggedInException(); 212 } 213 214 boolean mustPublishId = false; 215 if (getOmemoStoreBackend().isFreshInstallation(omemoManager)) { 216 LOGGER.log(Level.INFO, "No key material found. Looks like we have a fresh installation."); 217 // Create new key material and publish it to the server 218 regenerate(omemoManager, omemoManager.getDeviceId()); 219 mustPublishId = true; 220 } 221 222 // Get fresh device list from server 223 mustPublishId |= refreshOwnDeviceList(omemoManager); 224 225 publishDeviceIdIfNeeded(omemoManager, false, mustPublishId); 226 publishBundle(omemoManager); 227 228 registerOmemoMessageStanzaListeners(omemoManager); //Wait for new OMEMO messages 229 getOmemoStoreBackend().initializeOmemoSessions(omemoManager); //Preload existing OMEMO sessions 230 } 231 232 /** 233 * Test availability of required algorithms. We do this in advance, so we can simplify exception handling later. 234 * 235 * @throws NoSuchPaddingException 236 * @throws UnsupportedEncodingException 237 * @throws InvalidAlgorithmParameterException 238 * @throws NoSuchAlgorithmException 239 * @throws IllegalBlockSizeException 240 * @throws BadPaddingException 241 * @throws NoSuchProviderException 242 * @throws InvalidKeyException 243 */ 244 protected static void checkAvailableAlgorithms() throws NoSuchPaddingException, UnsupportedEncodingException, 245 InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, 246 NoSuchProviderException, InvalidKeyException { 247 // Test crypto functions 248 new OmemoMessageBuilder<>(null, null, ""); 249 } 250 251 /** 252 * Generate a new unique deviceId and regenerate new keys. 253 * 254 * @param omemoManager OmemoManager we want to regenerate. 255 * @param nDeviceId new DeviceId we want to use with the newly generated keys. 256 * @throws CorruptedOmemoKeyException when freshly generated identityKey is invalid 257 * (should never ever happen *crosses fingers*) 258 */ 259 void regenerate(OmemoManager omemoManager, Integer nDeviceId) throws CorruptedOmemoKeyException { 260 // Generate unique ID that is not already taken 261 while (nDeviceId == null || !getOmemoStoreBackend().isAvailableDeviceId(omemoManager, nDeviceId)) { 262 nDeviceId = OmemoManager.randomDeviceId(); 263 } 264 265 getOmemoStoreBackend().forgetOmemoSessions(omemoManager); 266 getOmemoStoreBackend().purgeOwnDeviceKeys(omemoManager); 267 omemoManager.setDeviceId(nDeviceId); 268 getOmemoStoreBackend().regenerate(omemoManager); 269 } 270 271 /** 272 * Publish a fresh bundle to the server. 273 * 274 * @param omemoManager OmemoManager 275 * @throws SmackException.NotConnectedException 276 * @throws InterruptedException 277 * @throws SmackException.NoResponseException 278 * @throws CorruptedOmemoKeyException 279 * @throws XMPPException.XMPPErrorException 280 */ 281 void publishBundle(OmemoManager omemoManager) 282 throws SmackException.NotConnectedException, InterruptedException, 283 SmackException.NoResponseException, CorruptedOmemoKeyException, XMPPException.XMPPErrorException { 284 Date lastSignedPreKeyRenewal = getOmemoStoreBackend().getDateOfLastSignedPreKeyRenewal(omemoManager); 285 if (OmemoConfiguration.getRenewOldSignedPreKeys() && lastSignedPreKeyRenewal != null) { 286 if (System.currentTimeMillis() - lastSignedPreKeyRenewal.getTime() 287 > 1000L * 60 * 60 * OmemoConfiguration.getRenewOldSignedPreKeysAfterHours()) { 288 LOGGER.log(Level.INFO, "Renewing signedPreKey"); 289 getOmemoStoreBackend().changeSignedPreKey(omemoManager); 290 } 291 } else { 292 getOmemoStoreBackend().setDateOfLastSignedPreKeyRenewal(omemoManager); 293 } 294 295 // publish 296 PubSubManager.getInstance(omemoManager.getConnection(), omemoManager.getOwnJid()) 297 .tryToPublishAndPossibleAutoCreate(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(omemoManager.getDeviceId()), 298 new PayloadItem<>(getOmemoStoreBackend().packOmemoBundle(omemoManager))); 299 } 300 301 /** 302 * Publish our deviceId in case it is not on the list already. 303 * This method calls publishDeviceIdIfNeeded(omemoManager, deleteOtherDevices, false). 304 * @param omemoManager OmemoManager 305 * @param deleteOtherDevices Do we want to remove other devices from the list? 306 * @throws InterruptedException 307 * @throws PubSubException.NotALeafNodeException 308 * @throws XMPPException.XMPPErrorException 309 * @throws SmackException.NotConnectedException 310 * @throws SmackException.NoResponseException 311 */ 312 void publishDeviceIdIfNeeded(OmemoManager omemoManager, boolean deleteOtherDevices) throws InterruptedException, 313 PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, 314 SmackException.NotConnectedException, SmackException.NoResponseException { 315 publishDeviceIdIfNeeded(omemoManager, deleteOtherDevices, false); 316 } 317 318 /** 319 * Publish our deviceId in case it is not on the list already. 320 * 321 * @param omemoManager OmemoManager 322 * @param deleteOtherDevices Do we want to remove other devices from the list? 323 * If we do, publish the list with only our id, regardless if we were on the list 324 * already. 325 * @param publish Do we want to force publishing our id? 326 * @throws SmackException.NotConnectedException 327 * @throws InterruptedException 328 * @throws SmackException.NoResponseException 329 * @throws XMPPException.XMPPErrorException 330 * @throws PubSubException.NotALeafNodeException 331 */ 332 void publishDeviceIdIfNeeded(OmemoManager omemoManager, boolean deleteOtherDevices, boolean publish) 333 throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, 334 XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException { 335 336 CachedDeviceList deviceList = getOmemoStoreBackend().loadCachedDeviceList(omemoManager, omemoManager.getOwnJid()); 337 338 Set<Integer> deviceListIds; 339 if (deviceList == null) { 340 deviceListIds = new HashSet<>(); 341 } else { 342 deviceListIds = new HashSet<>(deviceList.getActiveDevices()); 343 } 344 345 if (deleteOtherDevices) { 346 deviceListIds.clear(); 347 } 348 349 int ourDeviceId = omemoManager.getDeviceId(); 350 if (deviceListIds.add(ourDeviceId)) { 351 publish = true; 352 } 353 354 publish |= removeStaleDevicesIfNeeded(omemoManager, deviceListIds); 355 356 if (publish) { 357 publishDeviceIds(omemoManager, new OmemoDeviceListVAxolotlElement(deviceListIds)); 358 } 359 } 360 361 /** 362 * Remove stale devices from our device list. 363 * This does only delete devices, if that's configured in OmemoConfiguration. 364 * 365 * @param omemoManager OmemoManager 366 * @param deviceListIds deviceIds we plan to publish. Stale devices are deleted from that list. 367 * @return 368 */ 369 boolean removeStaleDevicesIfNeeded(OmemoManager omemoManager, Set<Integer> deviceListIds) { 370 boolean publish = false; 371 int ownDeviceId = omemoManager.getDeviceId(); 372 // Clear devices that we didn't receive a message from for a while 373 Iterator<Integer> it = deviceListIds.iterator(); 374 while (OmemoConfiguration.getDeleteStaleDevices() && it.hasNext()) { 375 int id = it.next(); 376 if (id == ownDeviceId) { 377 // Skip own id 378 continue; 379 } 380 381 OmemoDevice d = new OmemoDevice(omemoManager.getOwnJid(), id); 382 Date date = getOmemoStoreBackend().getDateOfLastReceivedMessage(omemoManager, d); 383 384 if (date == null) { 385 getOmemoStoreBackend().setDateOfLastReceivedMessage(omemoManager, d); 386 } else { 387 if (System.currentTimeMillis() - date.getTime() > 1000L * 60 * 60 * OmemoConfiguration.getDeleteStaleDevicesAfterHours()) { 388 LOGGER.log(Level.INFO, "Remove device " + id + " because of more than " + 389 OmemoConfiguration.getDeleteStaleDevicesAfterHours() + " hours of inactivity."); 390 it.remove(); 391 publish = true; 392 } 393 } 394 } 395 return publish; 396 } 397 398 /** 399 * Publish the given deviceList to the server. 400 * 401 * @param omemoManager OmemoManager 402 * @param deviceList list of deviceIDs 403 * @throws InterruptedException Exception 404 * @throws XMPPException.XMPPErrorException Exception 405 * @throws SmackException.NotConnectedException Exception 406 * @throws SmackException.NoResponseException Exception 407 * @throws PubSubException.NotALeafNodeException Exception 408 */ 409 static void publishDeviceIds(OmemoManager omemoManager, OmemoDeviceListElement deviceList) 410 throws InterruptedException, XMPPException.XMPPErrorException, 411 SmackException.NotConnectedException, SmackException.NoResponseException, PubSubException.NotALeafNodeException { 412 PubSubManager.getInstance(omemoManager.getConnection(), omemoManager.getOwnJid()) 413 .tryToPublishAndPossibleAutoCreate(OmemoConstants.PEP_NODE_DEVICE_LIST, new PayloadItem<>(deviceList)); 414 } 415 416 /** 417 * Fetch the deviceList node of a contact. 418 * 419 * @param omemoManager omemoManager 420 * @param contact contact 421 * @return LeafNode 422 * @throws InterruptedException 423 * @throws PubSubException.NotALeafNodeException 424 * @throws XMPPException.XMPPErrorException 425 * @throws SmackException.NotConnectedException 426 * @throws SmackException.NoResponseException 427 * @throws NotAPubSubNodeException 428 */ 429 static LeafNode fetchDeviceListNode(OmemoManager omemoManager, BareJid contact) 430 throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, 431 SmackException.NotConnectedException, SmackException.NoResponseException, NotAPubSubNodeException { 432 return PubSubManager.getInstance(omemoManager.getConnection(), contact).getLeafNode(PEP_NODE_DEVICE_LIST); 433 } 434 435 /** 436 * Directly fetch the device list of a contact. 437 * 438 * @param omemoManager OmemoManager 439 * @param contact BareJid of the contact 440 * @return The OmemoDeviceListElement of the contact 441 * @throws XMPPException.XMPPErrorException When 442 * @throws SmackException.NotConnectedException something 443 * @throws InterruptedException goes 444 * @throws SmackException.NoResponseException wrong 445 * @throws PubSubException.NotALeafNodeException when the device lists node is not a LeafNode 446 * @throws NotAPubSubNodeException 447 */ 448 static OmemoDeviceListElement fetchDeviceList(OmemoManager omemoManager, BareJid contact) 449 throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, 450 SmackException.NoResponseException, PubSubException.NotALeafNodeException, NotAPubSubNodeException { 451 return extractDeviceListFrom(fetchDeviceListNode(omemoManager, contact)); 452 } 453 454 /** 455 * Refresh our deviceList from the server. 456 * 457 * @param omemoManager omemoManager 458 * @return true, if we should publish our device list again (because its broken or not existent...) 459 * 460 * @throws SmackException.NotConnectedException 461 * @throws InterruptedException 462 * @throws SmackException.NoResponseException 463 */ 464 private boolean refreshOwnDeviceList(OmemoManager omemoManager) throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, XMPPException.XMPPErrorException { 465 try { 466 getOmemoStoreBackend().mergeCachedDeviceList(omemoManager, omemoManager.getOwnJid(), 467 fetchDeviceList(omemoManager, omemoManager.getOwnJid())); 468 469 } catch (XMPPException.XMPPErrorException e) { 470 471 if (e.getXMPPError().getCondition() == XMPPError.Condition.item_not_found) { 472 LOGGER.log(Level.WARNING, "Could not refresh own deviceList, because the node did not exist: " 473 + e.getMessage()); 474 return true; 475 } 476 477 throw e; 478 479 } catch (PubSubException.NotALeafNodeException e) { 480 LOGGER.log(Level.WARNING, "Could not refresh own deviceList, because the Node is not a LeafNode: " + 481 e.getMessage()); 482 } 483 484 catch (PubSubException.NotAPubSubNodeException e) { 485 LOGGER.log(Level.WARNING, "Caught a PubSubAssertionError when fetching a deviceList node. " + 486 "This probably means that we're dealing with an ejabberd server and the LeafNode does not exist.", e); 487 return true; 488 } 489 return false; 490 } 491 492 /** 493 * Refresh the deviceList of contact and merge it with the one stored locally. 494 * @param omemoManager omemoManager 495 * @param contact contact 496 * @throws SmackException.NotConnectedException 497 * @throws InterruptedException 498 * @throws SmackException.NoResponseException 499 */ 500 void refreshDeviceList(OmemoManager omemoManager, BareJid contact) throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { 501 OmemoDeviceListElement omemoDeviceListElement; 502 try { 503 omemoDeviceListElement = fetchDeviceList(omemoManager, contact); 504 } catch (PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException e) { 505 LOGGER.log(Level.WARNING, "Could not fetch device list of " + contact + ": " + e, e); 506 return; 507 } 508 catch (NotAPubSubNodeException e) { 509 LOGGER.log(Level.WARNING, "Could not fetch device list of " + contact ,e); 510 return; 511 } 512 513 getOmemoStoreBackend().mergeCachedDeviceList(omemoManager, contact, omemoDeviceListElement); 514 } 515 516 /** 517 * Fetch the OmemoBundleElement of the contact. 518 * 519 * @param omemoManager OmemoManager 520 * @param contact the contacts BareJid 521 * @return the OmemoBundleElement of the contact 522 * @throws XMPPException.XMPPErrorException When 523 * @throws SmackException.NotConnectedException something 524 * @throws InterruptedException goes 525 * @throws SmackException.NoResponseException wrong 526 * @throws PubSubException.NotALeafNodeException when the bundles node is not a LeafNode 527 * @throws NotAPubSubNodeException 528 */ 529 static OmemoBundleVAxolotlElement fetchBundle(OmemoManager omemoManager, OmemoDevice contact) 530 throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, 531 SmackException.NoResponseException, PubSubException.NotALeafNodeException, NotAPubSubNodeException { 532 LeafNode node = PubSubManager.getInstance(omemoManager.getConnection(), contact.getJid()).getLeafNode( 533 PEP_NODE_BUNDLE_FROM_DEVICE_ID(contact.getDeviceId())); 534 return extractBundleFrom(node); 535 } 536 537 /** 538 * Extract the OmemoBundleElement of a contact from a LeafNode. 539 * 540 * @param node typically a LeafNode containing the OmemoBundles of a contact 541 * @return the OmemoBundleElement 542 * @throws XMPPException.XMPPErrorException When 543 * @throws SmackException.NotConnectedException something 544 * @throws InterruptedException goes 545 * @throws SmackException.NoResponseException wrong 546 */ 547 private static OmemoBundleVAxolotlElement extractBundleFrom(LeafNode node) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { 548 if (node == null) { 549 return null; 550 } 551 try { 552 return (OmemoBundleVAxolotlElement) ((PayloadItem<?>) node.getItems().get(0)).getPayload(); 553 } catch (IndexOutOfBoundsException e) { 554 return null; 555 } 556 } 557 558 /** 559 * Extract the OmemoDeviceListElement of a contact from a node containing his OmemoDeviceListElement. 560 * 561 * @param node typically a LeafNode containing the OmemoDeviceListElement of a contact 562 * @return the extracted OmemoDeviceListElement. 563 * @throws XMPPException.XMPPErrorException When 564 * @throws SmackException.NotConnectedException something 565 * @throws InterruptedException goes 566 * @throws SmackException.NoResponseException wrong 567 */ 568 private static OmemoDeviceListElement extractDeviceListFrom(LeafNode node) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { 569 if (node == null) { 570 LOGGER.log(Level.WARNING, "DeviceListNode is null."); 571 return null; 572 } 573 List<?> items = node.getItems(); 574 if (items.size() > 0) { 575 OmemoDeviceListVAxolotlElement listElement = (OmemoDeviceListVAxolotlElement) ((PayloadItem<?>) items.get(items.size() - 1)).getPayload(); 576 if (items.size() > 1) { 577 node.deleteAllItems(); 578 node.publish(new PayloadItem<>(listElement)); 579 } 580 return listElement; 581 } 582 583 Set<Integer> emptySet = Collections.emptySet(); 584 return new OmemoDeviceListVAxolotlElement(emptySet); 585 } 586 587 /** 588 * Build sessions for all devices of the contact that we do not have a session with yet. 589 * 590 * @param omemoManager omemoManager 591 * @param jid the BareJid of the contact 592 */ 593 void buildOrCreateOmemoSessionsFromBundles(OmemoManager omemoManager, BareJid jid) throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, CannotEstablishOmemoSessionException { 594 CachedDeviceList devices = getOmemoStoreBackend().loadCachedDeviceList(omemoManager, jid); 595 CannotEstablishOmemoSessionException sessionException = null; 596 if (devices == null || devices.getAllDevices().isEmpty()) { 597 refreshDeviceList(omemoManager, jid); 598 devices = getOmemoStoreBackend().loadCachedDeviceList(omemoManager, jid); 599 } 600 601 for (int id : devices.getActiveDevices()) { 602 OmemoDevice device = new OmemoDevice(jid, id); 603 if (getOmemoStoreBackend().containsRawSession(omemoManager, device)) { 604 // We have a session already. 605 continue; 606 } 607 608 // Build missing session 609 try { 610 buildSessionFromOmemoBundle(omemoManager, device, false); 611 } catch (CannotEstablishOmemoSessionException e) { 612 613 if (sessionException == null) { 614 sessionException = e; 615 } else { 616 sessionException.addFailures(e); 617 } 618 619 } catch (CorruptedOmemoKeyException e) { 620 CannotEstablishOmemoSessionException fail = 621 new CannotEstablishOmemoSessionException(device, e); 622 623 if (sessionException == null) { 624 sessionException = fail; 625 } else { 626 sessionException.addFailures(fail); 627 } 628 } 629 } 630 631 if (sessionException != null) { 632 throw sessionException; 633 } 634 } 635 636 /** 637 * Build an OmemoSession for the given OmemoDevice. 638 * 639 * @param omemoManager omemoManager 640 * @param device OmemoDevice 641 * @param fresh Do we want to build a session even if we already have one? 642 * @throws CannotEstablishOmemoSessionException when no session could be established 643 * @throws CorruptedOmemoKeyException when the bundle contained an invalid OMEMO identityKey 644 */ 645 public void buildSessionFromOmemoBundle(OmemoManager omemoManager, OmemoDevice device, boolean fresh) throws CannotEstablishOmemoSessionException, CorruptedOmemoKeyException { 646 647 if (device.equals(omemoManager.getOwnDevice())) { 648 return; 649 } 650 651 // Do not build sessions with devices we already know... 652 if (!fresh && getOmemoStoreBackend().containsRawSession(omemoManager, device)) { 653 getOmemoStoreBackend().getOmemoSessionOf(omemoManager, device); //Make sure its loaded though 654 return; 655 } 656 657 OmemoBundleVAxolotlElement bundle; 658 try { 659 bundle = fetchBundle(omemoManager, device); 660 } catch (SmackException | XMPPException.XMPPErrorException | InterruptedException e) { 661 throw new CannotEstablishOmemoSessionException(device, e); 662 } 663 664 HashMap<Integer, T_Bundle> bundles = getOmemoStoreBackend().keyUtil().BUNDLE.bundles(bundle, device); 665 666 // Select random Bundle 667 int randomIndex = new Random().nextInt(bundles.size()); 668 T_Bundle randomPreKeyBundle = new ArrayList<>(bundles.values()).get(randomIndex); 669 // Build raw session 670 processBundle(omemoManager, randomPreKeyBundle, device); 671 } 672 673 /** 674 * Process a received bundle. Typically that includes saving keys and building a session. 675 * 676 * @param omemoManager omemoManager that will process the bundle 677 * @param bundle T_Bundle (depends on used Signal/Olm library) 678 * @param device OmemoDevice 679 * @throws CorruptedOmemoKeyException 680 */ 681 protected abstract void processBundle(OmemoManager omemoManager, T_Bundle bundle, OmemoDevice device) throws CorruptedOmemoKeyException; 682 683 /** 684 * Process a received message. Try to decrypt it in case we are a recipient device. If we are not a recipient 685 * device, return null. 686 * 687 * @param sender the BareJid of the sender of the message 688 * @param message the encrypted message 689 * @param information OmemoMessageInformation object which will contain meta data about the decrypted message 690 * @return decrypted message or null 691 * @throws NoRawSessionException 692 * @throws InterruptedException 693 * @throws SmackException.NoResponseException 694 * @throws SmackException.NotConnectedException 695 * @throws CryptoFailedException 696 * @throws XMPPException.XMPPErrorException 697 * @throws CorruptedOmemoKeyException 698 */ 699 private Message processReceivingMessage(OmemoManager omemoManager, OmemoDevice sender, OmemoElement message, final OmemoMessageInformation information) 700 throws NoRawSessionException, InterruptedException, SmackException.NoResponseException, SmackException.NotConnectedException, 701 CryptoFailedException, XMPPException.XMPPErrorException, CorruptedOmemoKeyException { 702 703 ArrayList<OmemoVAxolotlElement.OmemoHeader.Key> messageRecipientKeys = message.getHeader().getKeys(); 704 // Do we have a key with our ID in the message? 705 for (OmemoVAxolotlElement.OmemoHeader.Key k : messageRecipientKeys) { 706 // Only decrypt with our deviceID 707 if (k.getId() != omemoManager.getDeviceId()) { 708 continue; 709 } 710 711 Message decrypted = decryptOmemoMessageElement(omemoManager, sender, message, information); 712 if (sender.equals(omemoManager.getOwnJid()) && decrypted != null) { 713 getOmemoStoreBackend().setDateOfLastReceivedMessage(omemoManager, sender); 714 } 715 return decrypted; 716 } 717 718 LOGGER.log(Level.INFO, "There is no key with our deviceId. Silently discard the message."); 719 return null; 720 } 721 722 /** 723 * Decrypt a given OMEMO encrypted message. Return null, if there is no OMEMO element in the message, 724 * otherwise try to decrypt the message and return a ClearTextMessage object. 725 * 726 * @param omemoManager omemoManager of the receiving device 727 * @param sender barejid of the sender 728 * @param message encrypted message 729 * @return decrypted message or null 730 * @throws InterruptedException Exception 731 * @throws SmackException.NoResponseException Exception 732 * @throws SmackException.NotConnectedException Exception 733 * @throws CryptoFailedException When the message could not be decrypted. 734 * @throws XMPPException.XMPPErrorException Exception 735 * @throws CorruptedOmemoKeyException When the used OMEMO keys are invalid. 736 * @throws NoRawSessionException When there is no session to decrypt the message with in the double 737 * ratchet library 738 */ 739 ClearTextMessage processLocalMessage(OmemoManager omemoManager, BareJid sender, Message message) throws InterruptedException, SmackException.NoResponseException, SmackException.NotConnectedException, CryptoFailedException, XMPPException.XMPPErrorException, CorruptedOmemoKeyException, NoRawSessionException { 740 if (OmemoManager.stanzaContainsOmemoElement(message)) { 741 OmemoElement omemoMessageElement = message.getExtension(OmemoElement.ENCRYPTED, OMEMO_NAMESPACE_V_AXOLOTL); 742 OmemoMessageInformation info = new OmemoMessageInformation(); 743 Message decrypted = processReceivingMessage(omemoManager, 744 new OmemoDevice(sender, omemoMessageElement.getHeader().getSid()), 745 omemoMessageElement, info); 746 return new ClearTextMessage(decrypted != null ? decrypted.getBody() : null, message, info); 747 } else { 748 LOGGER.log(Level.WARNING, "Stanza does not contain an OMEMO message."); 749 return null; 750 } 751 } 752 753 /** 754 * Encrypt a clear text message for the given recipient. 755 * The body of the message will be encrypted. 756 * 757 * @param omemoManager omemoManager of the sending device 758 * @param recipient BareJid of the recipient 759 * @param message message to encrypt. 760 * @return OmemoMessageElement 761 * @throws CryptoFailedException 762 * @throws UndecidedOmemoIdentityException 763 * @throws NoSuchAlgorithmException 764 */ 765 OmemoVAxolotlElement processSendingMessage(OmemoManager omemoManager, BareJid recipient, Message message) 766 throws CryptoFailedException, UndecidedOmemoIdentityException, NoSuchAlgorithmException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, CannotEstablishOmemoSessionException { 767 ArrayList<BareJid> recipients = new ArrayList<>(); 768 recipients.add(recipient); 769 return processSendingMessage(omemoManager, recipients, message); 770 } 771 772 /** 773 * Encrypt a clear text message for the given recipients. 774 * The body of the message will be encrypted. 775 * 776 * @param omemoManager omemoManager of the sending device. 777 * @param recipients List of BareJids of all recipients 778 * @param message message to encrypt. 779 * @return OmemoMessageElement 780 * @throws CryptoFailedException 781 * @throws UndecidedOmemoIdentityException 782 * @throws NoSuchAlgorithmException 783 */ 784 OmemoVAxolotlElement processSendingMessage(OmemoManager omemoManager, ArrayList<BareJid> recipients, Message message) 785 throws CryptoFailedException, UndecidedOmemoIdentityException, NoSuchAlgorithmException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, CannotEstablishOmemoSessionException { 786 787 CannotEstablishOmemoSessionException sessionException = null; 788 // Them - The contact wants to read the message on all their devices. 789 HashMap<BareJid, ArrayList<OmemoDevice>> receivers = new HashMap<>(); 790 for (BareJid recipient : recipients) { 791 try { 792 buildOrCreateOmemoSessionsFromBundles(omemoManager, recipient); 793 } catch (CannotEstablishOmemoSessionException e) { 794 795 if (sessionException == null) { 796 sessionException = e; 797 } else { 798 sessionException.addFailures(e); 799 } 800 } 801 } 802 803 for (BareJid recipient : recipients) { 804 CachedDeviceList theirDevices = getOmemoStoreBackend().loadCachedDeviceList(omemoManager, recipient); 805 ArrayList<OmemoDevice> receivingDevices = new ArrayList<>(); 806 for (int id : theirDevices.getActiveDevices()) { 807 OmemoDevice recipientDevice = new OmemoDevice(recipient, id); 808 809 if (getOmemoStoreBackend().containsRawSession(omemoManager, recipientDevice)) { 810 receivingDevices.add(recipientDevice); 811 } 812 813 if (sessionException != null) { 814 sessionException.addSuccess(recipientDevice); 815 } 816 } 817 818 if (!receivingDevices.isEmpty()) { 819 receivers.put(recipient, receivingDevices); 820 } 821 } 822 823 // Us - We want to read the message on all of our devices 824 CachedDeviceList ourDevices = getOmemoStoreBackend().loadCachedDeviceList(omemoManager, omemoManager.getOwnJid()); 825 if (ourDevices == null) { 826 ourDevices = new CachedDeviceList(); 827 } 828 829 ArrayList<OmemoDevice> ourReceivingDevices = new ArrayList<>(); 830 for (int id : ourDevices.getActiveDevices()) { 831 OmemoDevice ourDevice = new OmemoDevice(omemoManager.getOwnJid(), id); 832 if (id == omemoManager.getDeviceId()) { 833 // Don't build session with our exact device. 834 continue; 835 } 836 837 Date lastReceived = getOmemoStoreBackend().getDateOfLastReceivedMessage(omemoManager, ourDevice); 838 if (lastReceived == null) { 839 getOmemoStoreBackend().setDateOfLastReceivedMessage(omemoManager, ourDevice); 840 lastReceived = new Date(); 841 } 842 843 if (OmemoConfiguration.getIgnoreStaleDevices() && System.currentTimeMillis() - lastReceived.getTime() 844 > 1000L * 60 * 60 * OmemoConfiguration.getIgnoreStaleDevicesAfterHours()) { 845 LOGGER.log(Level.WARNING, "Refusing to encrypt message for stale device " + ourDevice + 846 " which was inactive for at least " + OmemoConfiguration.getIgnoreStaleDevicesAfterHours() + 847 " hours."); 848 } else { 849 if (getOmemoStoreBackend().containsRawSession(omemoManager, ourDevice)) { 850 ourReceivingDevices.add(ourDevice); 851 } 852 } 853 } 854 855 if (!ourReceivingDevices.isEmpty()) { 856 receivers.put(omemoManager.getOwnJid(), ourReceivingDevices); 857 } 858 859 if (sessionException != null && sessionException.requiresThrowing()) { 860 throw sessionException; 861 } 862 863 return encryptOmemoMessage(omemoManager, receivers, message); 864 } 865 866 /** 867 * Decrypt a incoming OmemoMessageElement that was sent by the OmemoDevice 'from'. 868 * 869 * @param omemoManager omemoManager of the decrypting device. 870 * @param from OmemoDevice that sent the message 871 * @param message Encrypted OmemoMessageElement 872 * @param information OmemoMessageInformation object which will contain metadata about the encryption 873 * @return Decrypted message 874 * @throws CryptoFailedException when decrypting message fails for some reason 875 * @throws InterruptedException 876 * @throws CorruptedOmemoKeyException 877 * @throws XMPPException.XMPPErrorException 878 * @throws SmackException.NotConnectedException 879 * @throws SmackException.NoResponseException 880 * @throws NoRawSessionException 881 */ 882 private Message decryptOmemoMessageElement(OmemoManager omemoManager, OmemoDevice from, OmemoElement message, 883 final OmemoMessageInformation information) 884 throws CryptoFailedException, InterruptedException, CorruptedOmemoKeyException, XMPPException.XMPPErrorException, 885 SmackException.NotConnectedException, SmackException.NoResponseException, NoRawSessionException { 886 887 CipherAndAuthTag transportedKey = decryptTransportedOmemoKey(omemoManager, from, message, information); 888 return OmemoSession.decryptMessageElement(message, transportedKey); 889 } 890 891 /** 892 * Decrypt a messageKey that was transported in an OmemoElement. 893 * 894 * @param omemoManager omemoManager of the receiving device. 895 * @param sender omemoDevice of the sender. 896 * @param omemoMessage omemoElement containing the key. 897 * @param messageInfo omemoMessageInformation that will contain metadata about the encryption. 898 * @return a CipherAndAuthTag pair 899 * @throws CryptoFailedException 900 * @throws NoRawSessionException 901 * @throws InterruptedException 902 * @throws CorruptedOmemoKeyException 903 * @throws XMPPException.XMPPErrorException 904 * @throws SmackException.NotConnectedException 905 * @throws SmackException.NoResponseException 906 */ 907 private CipherAndAuthTag decryptTransportedOmemoKey(OmemoManager omemoManager, OmemoDevice sender, 908 OmemoElement omemoMessage, 909 OmemoMessageInformation messageInfo) 910 throws CryptoFailedException, NoRawSessionException, InterruptedException, CorruptedOmemoKeyException, 911 XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException { 912 913 int preKeyCountBefore = getOmemoStoreBackend().loadOmemoPreKeys(omemoManager).size(); 914 915 OmemoSession<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> 916 session = getOmemoStoreBackend().getOmemoSessionOf(omemoManager, sender); 917 CipherAndAuthTag cipherAndAuthTag = session.decryptTransportedKey(omemoMessage, omemoManager.getDeviceId()); 918 919 messageInfo.setSenderDevice(sender); 920 messageInfo.setSenderIdentityKey(new IdentityKeyWrapper(session.getIdentityKey())); 921 922 if (preKeyCountBefore != getOmemoStoreBackend().loadOmemoPreKeys(omemoManager).size()) { 923 LOGGER.log(Level.INFO, "We used up a preKey. Publish new Bundle."); 924 publishBundle(omemoManager); 925 } 926 return cipherAndAuthTag; 927 } 928 929 /** 930 * Encrypt the message and return it as an OmemoMessageElement. 931 * 932 * @param omemoManager omemoManager of the encrypting device. 933 * @param recipients List of devices that will be able to decipher the message. 934 * @param message Clear text message 935 * 936 * @throws CryptoFailedException when some cryptographic function fails 937 * @throws UndecidedOmemoIdentityException when the identity of one or more contacts is undecided 938 * 939 * @return OmemoMessageElement 940 */ 941 OmemoVAxolotlElement encryptOmemoMessage(OmemoManager omemoManager, HashMap<BareJid, ArrayList<OmemoDevice>> recipients, Message message) 942 throws CryptoFailedException, UndecidedOmemoIdentityException { 943 944 OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> 945 builder; 946 try { 947 builder = new OmemoMessageBuilder<>(omemoManager, getOmemoStoreBackend(), message.getBody()); 948 } catch (UnsupportedEncodingException | BadPaddingException | IllegalBlockSizeException | NoSuchProviderException | 949 NoSuchPaddingException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException e) { 950 throw new CryptoFailedException(e); 951 } 952 953 UndecidedOmemoIdentityException undecided = null; 954 955 for (Map.Entry<BareJid, ArrayList<OmemoDevice>> entry : recipients.entrySet()) { 956 for (OmemoDevice c : entry.getValue()) { 957 try { 958 builder.addRecipient(c); 959 } catch (CorruptedOmemoKeyException e) { 960 // TODO: How to react? 961 LOGGER.log(Level.SEVERE, "encryptOmemoMessage failed to establish a session with device " 962 + c + ": " + e.getMessage()); 963 } catch (UndecidedOmemoIdentityException e) { 964 // Collect all undecided devices 965 if (undecided == null) { 966 undecided = e; 967 } else { 968 undecided.join(e); 969 } 970 } 971 } 972 } 973 974 if (undecided != null) { 975 throw undecided; 976 } 977 978 return builder.finish(); 979 } 980 981 /** 982 * Prepares a keyTransportElement with a random aes key and iv. 983 * 984 * @param omemoManager omemoManager of the sending device. 985 * @param recipients recipients of the omemoKeyTransportElement 986 * @return KeyTransportElement 987 * @throws CryptoFailedException 988 * @throws UndecidedOmemoIdentityException 989 * @throws CorruptedOmemoKeyException 990 * @throws CannotEstablishOmemoSessionException 991 */ 992 OmemoVAxolotlElement prepareOmemoKeyTransportElement(OmemoManager omemoManager, OmemoDevice... recipients) throws CryptoFailedException, 993 UndecidedOmemoIdentityException, CorruptedOmemoKeyException, CannotEstablishOmemoSessionException { 994 995 OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> 996 builder; 997 try { 998 builder = new OmemoMessageBuilder<>(omemoManager, getOmemoStoreBackend(), null); 999 1000 } catch (UnsupportedEncodingException | BadPaddingException | IllegalBlockSizeException | NoSuchProviderException | 1001 NoSuchPaddingException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException e) { 1002 throw new CryptoFailedException(e); 1003 } 1004 1005 for (OmemoDevice r : recipients) { 1006 builder.addRecipient(r); 1007 } 1008 1009 return builder.finish(); 1010 } 1011 1012 /** 1013 * Prepare a KeyTransportElement with aesKey and iv. 1014 * 1015 * @param omemoManager OmemoManager of the sending device. 1016 * @param aesKey AES key 1017 * @param iv initialization vector 1018 * @param recipients recipients 1019 * @return KeyTransportElement 1020 * @throws CryptoFailedException 1021 * @throws UndecidedOmemoIdentityException 1022 * @throws CorruptedOmemoKeyException 1023 * @throws CannotEstablishOmemoSessionException 1024 */ 1025 OmemoVAxolotlElement prepareOmemoKeyTransportElement(OmemoManager omemoManager, byte[] aesKey, byte[] iv, OmemoDevice... recipients) throws CryptoFailedException, 1026 UndecidedOmemoIdentityException, CorruptedOmemoKeyException, CannotEstablishOmemoSessionException { 1027 1028 OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> 1029 builder; 1030 try { 1031 builder = new OmemoMessageBuilder<>(omemoManager, getOmemoStoreBackend(), aesKey, iv); 1032 1033 } catch (UnsupportedEncodingException | BadPaddingException | IllegalBlockSizeException | NoSuchProviderException | 1034 NoSuchPaddingException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException e) { 1035 throw new CryptoFailedException(e); 1036 } 1037 1038 for (OmemoDevice r : recipients) { 1039 builder.addRecipient(r); 1040 } 1041 1042 return builder.finish(); 1043 } 1044 1045 /** 1046 * Return a new RatchetUpdateMessage. 1047 * 1048 * @param omemoManager omemoManager of the sending device. 1049 * @param recipient recipient 1050 * @param preKeyMessage if true, a new session will be built for this message (useful to repair broken sessions) 1051 * otherwise the message will be encrypted using the existing session. 1052 * @return OmemoRatchetUpdateMessage 1053 * @throws CannotEstablishOmemoSessionException 1054 * @throws CorruptedOmemoKeyException 1055 * @throws CryptoFailedException 1056 * @throws UndecidedOmemoIdentityException 1057 */ 1058 protected Message getOmemoRatchetUpdateMessage(OmemoManager omemoManager, OmemoDevice recipient, boolean preKeyMessage) throws CannotEstablishOmemoSessionException, CorruptedOmemoKeyException, CryptoFailedException, UndecidedOmemoIdentityException { 1059 if (preKeyMessage) { 1060 buildSessionFromOmemoBundle(omemoManager, recipient, true); 1061 } 1062 1063 OmemoVAxolotlElement keyTransportElement = prepareOmemoKeyTransportElement(omemoManager, recipient); 1064 Message ratchetUpdateMessage = omemoManager.finishMessage(keyTransportElement); 1065 ratchetUpdateMessage.setTo(recipient.getJid()); 1066 1067 return ratchetUpdateMessage; 1068 } 1069 1070 /** 1071 * Send an OmemoRatchetUpdateMessage to recipient. If preKeyMessage is true, the message will be encrypted using a 1072 * freshly built session. This can be used to repair broken sessions. 1073 * 1074 * @param omemoManager omemoManager of the sending device. 1075 * @param recipient recipient 1076 * @param preKeyMessage shall this be a preKeyMessage? 1077 * @throws UndecidedOmemoIdentityException 1078 * @throws CorruptedOmemoKeyException 1079 * @throws CryptoFailedException 1080 * @throws CannotEstablishOmemoSessionException 1081 */ 1082 protected void sendOmemoRatchetUpdateMessage(OmemoManager omemoManager, OmemoDevice recipient, boolean preKeyMessage) throws UndecidedOmemoIdentityException, CorruptedOmemoKeyException, CryptoFailedException, CannotEstablishOmemoSessionException { 1083 Message ratchetUpdateMessage = getOmemoRatchetUpdateMessage(omemoManager, recipient, preKeyMessage); 1084 1085 try { 1086 omemoManager.getConnection().sendStanza(ratchetUpdateMessage); 1087 1088 } catch (SmackException.NotConnectedException | InterruptedException e) { 1089 LOGGER.log(Level.WARNING, "sendOmemoRatchetUpdateMessage failed: " + e.getMessage()); 1090 } 1091 } 1092 1093 /** 1094 * Listen for incoming messages and carbons, decrypt them and pass the cleartext messages to the registered 1095 * OmemoMessageListeners. 1096 * 1097 * @param omemoManager omemoManager we want to register with 1098 */ 1099 private void registerOmemoMessageStanzaListeners(OmemoManager omemoManager) { 1100 omemoManager.getConnection().removeAsyncStanzaListener(omemoManager.getOmemoStanzaListener()); 1101 omemoManager.getConnection().addAsyncStanzaListener(omemoManager.getOmemoStanzaListener(), omemoStanzaFilter); 1102 1103 CarbonManager.getInstanceFor(omemoManager.getConnection()).removeCarbonCopyReceivedListener(omemoManager.getOmemoCarbonCopyListener()); 1104 CarbonManager.getInstanceFor(omemoManager.getConnection()).addCarbonCopyReceivedListener(omemoManager.getOmemoCarbonCopyListener()); 1105 } 1106 1107 /** 1108 * StanzaFilter that filters messages containing a OMEMO element. 1109 */ 1110 private final StanzaFilter omemoStanzaFilter = new StanzaFilter() { 1111 @Override 1112 public boolean accept(Stanza stanza) { 1113 return stanza instanceof Message && OmemoManager.stanzaContainsOmemoElement(stanza); 1114 } 1115 }; 1116 1117 /** 1118 * Try to decrypt a mamQueryResult. Note that OMEMO messages can only be decrypted once on a device, so if you 1119 * try to decrypt a message that has been decrypted earlier in time, the decryption will fail. You should handle 1120 * message history locally when using OMEMO, since you cannot rely on MAM. 1121 * 1122 * @param omemoManager omemoManager of the decrypting device. 1123 * @param mamQueryResult mamQueryResult that shall be decrypted. 1124 * @return list of decrypted messages. 1125 * @throws InterruptedException 1126 * @throws XMPPException.XMPPErrorException 1127 * @throws SmackException.NotConnectedException 1128 * @throws SmackException.NoResponseException 1129 */ 1130 List<ClearTextMessage> decryptMamQueryResult(OmemoManager omemoManager, MamManager.MamQueryResult mamQueryResult) 1131 throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException { 1132 List<ClearTextMessage> result = new ArrayList<>(); 1133 for (Forwarded f : mamQueryResult.forwardedMessages) { 1134 if (OmemoManager.stanzaContainsOmemoElement(f.getForwardedStanza())) { 1135 // Decrypt OMEMO messages 1136 try { 1137 result.add(processLocalMessage(omemoManager, f.getForwardedStanza().getFrom().asBareJid(), (Message) f.getForwardedStanza())); 1138 } catch (NoRawSessionException | CorruptedOmemoKeyException | CryptoFailedException e) { 1139 LOGGER.log(Level.WARNING, "decryptMamQueryResult failed to decrypt message from " 1140 + f.getForwardedStanza().getFrom() + " due to corrupted session/key: " + e.getMessage()); 1141 } 1142 } else { 1143 // Wrap cleartext messages 1144 Message m = (Message) f.getForwardedStanza(); 1145 result.add(new ClearTextMessage(m.getBody(), m, 1146 new OmemoMessageInformation(null, null, OmemoMessageInformation.CARBON.NONE, false))); 1147 } 1148 } 1149 return result; 1150 } 1151 1152 /** 1153 * Return the barejid of the user that sent the message inside the MUC. If the message wasn't sent in a MUC, 1154 * return null; 1155 * 1156 * @param omemoManager omemoManager 1157 * @param stanza message 1158 * @return BareJid of the sender. 1159 */ 1160 private static OmemoDevice getSender(OmemoManager omemoManager, Stanza stanza) { 1161 OmemoElement omemoElement = stanza.getExtension(OmemoElement.ENCRYPTED, OMEMO_NAMESPACE_V_AXOLOTL); 1162 Jid sender = stanza.getFrom(); 1163 if (isMucMessage(omemoManager, stanza)) { 1164 MultiUserChatManager mucm = MultiUserChatManager.getInstanceFor(omemoManager.getConnection()); 1165 MultiUserChat muc = mucm.getMultiUserChat(sender.asEntityBareJidIfPossible()); 1166 sender = muc.getOccupant(sender.asEntityFullJidIfPossible()).getJid().asBareJid(); 1167 } 1168 if (sender == null) { 1169 throw new AssertionError("Sender is null."); 1170 } 1171 return new OmemoDevice(sender.asBareJid(), omemoElement.getHeader().getSid()); 1172 } 1173 1174 /** 1175 * Return true, if the user knows a multiUserChat with a jid matching the sender of the stanza. 1176 * @param omemoManager omemoManager of the user 1177 * @param stanza stanza in question 1178 * @return true if MUC message, otherwise false. 1179 */ 1180 private static boolean isMucMessage(OmemoManager omemoManager, Stanza stanza) { 1181 BareJid sender = stanza.getFrom().asBareJid(); 1182 MultiUserChatManager mucm = MultiUserChatManager.getInstanceFor(omemoManager.getConnection()); 1183 1184 return mucm.getJoinedRooms().contains(sender.asEntityBareJidIfPossible()); 1185 } 1186 1187 OmemoStanzaListener createStanzaListener(OmemoManager omemoManager) { 1188 return new OmemoStanzaListener(omemoManager, this); 1189 } 1190 1191 /** 1192 * StanzaListener that listens for incoming omemoElements that are NOT send via carbons. 1193 */ 1194 class OmemoStanzaListener implements StanzaListener { 1195 private final OmemoManager omemoManager; 1196 private final OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> 1197 service; 1198 1199 OmemoStanzaListener(OmemoManager omemoManager, 1200 OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> service) { 1201 this.omemoManager = omemoManager; 1202 this.service = service; 1203 } 1204 1205 @Override 1206 public void processStanza(Stanza stanza) throws SmackException.NotConnectedException, InterruptedException { 1207 Message decrypted; 1208 OmemoElement omemoMessage = stanza.getExtension(OmemoElement.ENCRYPTED, OMEMO_NAMESPACE_V_AXOLOTL); 1209 OmemoMessageInformation messageInfo = new OmemoMessageInformation(); 1210 MultiUserChatManager mucm = MultiUserChatManager.getInstanceFor(omemoManager.getConnection()); 1211 OmemoDevice senderDevice = getSender(omemoManager, stanza); 1212 try { 1213 // Is it a MUC message... 1214 if (isMucMessage(omemoManager, stanza)) { 1215 1216 MultiUserChat muc = mucm.getMultiUserChat(stanza.getFrom().asEntityBareJidIfPossible()); 1217 if (omemoMessage.isMessageElement()) { 1218 1219 decrypted = processReceivingMessage(omemoManager, senderDevice, omemoMessage, messageInfo); 1220 if (decrypted != null) { 1221 omemoManager.notifyOmemoMucMessageReceived(muc, senderDevice.getJid(), decrypted.getBody(), 1222 (Message) stanza, null, messageInfo); 1223 } 1224 1225 } else if (omemoMessage.isKeyTransportElement()) { 1226 1227 CipherAndAuthTag cipherAndAuthTag = decryptTransportedOmemoKey(omemoManager, senderDevice, omemoMessage, messageInfo); 1228 if (cipherAndAuthTag != null) { 1229 omemoManager.notifyOmemoMucKeyTransportMessageReceived(muc, senderDevice.getJid(), cipherAndAuthTag, 1230 (Message) stanza, null, messageInfo); 1231 } 1232 } 1233 } 1234 // ... or a normal chat message... 1235 else { 1236 if (omemoMessage.isMessageElement()) { 1237 1238 decrypted = service.processReceivingMessage(omemoManager, senderDevice, omemoMessage, messageInfo); 1239 if (decrypted != null) { 1240 omemoManager.notifyOmemoMessageReceived(decrypted.getBody(), (Message) stanza, null, messageInfo); 1241 } 1242 1243 } else if (omemoMessage.isKeyTransportElement()) { 1244 1245 CipherAndAuthTag cipherAndAuthTag = decryptTransportedOmemoKey(omemoManager, senderDevice, omemoMessage, messageInfo); 1246 if (cipherAndAuthTag != null) { 1247 omemoManager.notifyOmemoKeyTransportMessageReceived(cipherAndAuthTag, (Message) stanza, null, messageInfo); 1248 } 1249 } 1250 } 1251 1252 } catch (CryptoFailedException | CorruptedOmemoKeyException | InterruptedException | SmackException.NotConnectedException | XMPPException.XMPPErrorException | SmackException.NoResponseException e) { 1253 LOGGER.log(Level.WARNING, "internal omemoMessageListener failed to decrypt incoming OMEMO message: " 1254 + e.getMessage()); 1255 1256 } catch (NoRawSessionException e) { 1257 try { 1258 LOGGER.log(Level.INFO, "Received message with invalid session from " + 1259 senderDevice + ". Send RatchetUpdateMessage."); 1260 service.sendOmemoRatchetUpdateMessage(omemoManager, senderDevice, true); 1261 1262 } catch (UndecidedOmemoIdentityException | CorruptedOmemoKeyException | CannotEstablishOmemoSessionException | CryptoFailedException e1) { 1263 LOGGER.log(Level.WARNING, "internal omemoMessageListener failed to establish a session for incoming OMEMO message: " 1264 + e.getMessage()); 1265 } 1266 } 1267 } 1268 } 1269 1270 OmemoCarbonCopyListener createOmemoCarbonCopyListener(OmemoManager omemoManager) { 1271 return new OmemoCarbonCopyListener(omemoManager, this, omemoStanzaFilter); 1272 } 1273 1274 /** 1275 * StanzaListener that listens for incoming OmemoElements that ARE sent in carbons. 1276 */ 1277 class OmemoCarbonCopyListener implements CarbonCopyReceivedListener { 1278 1279 private final OmemoManager omemoManager; 1280 private final OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> service; 1281 private final StanzaFilter filter; 1282 1283 public OmemoCarbonCopyListener(OmemoManager omemoManager, 1284 OmemoService<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> service, 1285 StanzaFilter filter) { 1286 this.omemoManager = omemoManager; 1287 this.service = service; 1288 this.filter = filter; 1289 } 1290 1291 @Override 1292 public void onCarbonCopyReceived(CarbonExtension.Direction direction, Message carbonCopy, Message wrappingMessage) { 1293 if (filter.accept(carbonCopy)) { 1294 final OmemoDevice senderDevice = getSender(omemoManager, carbonCopy); 1295 Message decrypted; 1296 MultiUserChatManager mucm = MultiUserChatManager.getInstanceFor(omemoManager.getConnection()); 1297 OmemoElement omemoMessage = carbonCopy.getExtension(OmemoElement.ENCRYPTED, OMEMO_NAMESPACE_V_AXOLOTL); 1298 OmemoMessageInformation messageInfo = new OmemoMessageInformation(); 1299 1300 if (CarbonExtension.Direction.received.equals(direction)) { 1301 messageInfo.setCarbon(OmemoMessageInformation.CARBON.RECV); 1302 } else { 1303 messageInfo.setCarbon(OmemoMessageInformation.CARBON.SENT); 1304 } 1305 1306 try { 1307 // Is it a MUC message... 1308 if (isMucMessage(omemoManager, carbonCopy)) { 1309 1310 MultiUserChat muc = mucm.getMultiUserChat(carbonCopy.getFrom().asEntityBareJidIfPossible()); 1311 if (omemoMessage.isMessageElement()) { 1312 1313 decrypted = processReceivingMessage(omemoManager, senderDevice, omemoMessage, messageInfo); 1314 if (decrypted != null) { 1315 omemoManager.notifyOmemoMucMessageReceived(muc, senderDevice.getJid(), decrypted.getBody(), 1316 carbonCopy, wrappingMessage, messageInfo); 1317 } 1318 1319 } else if (omemoMessage.isKeyTransportElement()) { 1320 1321 CipherAndAuthTag cipherAndAuthTag = decryptTransportedOmemoKey(omemoManager, senderDevice, omemoMessage, messageInfo); 1322 if (cipherAndAuthTag != null) { 1323 omemoManager.notifyOmemoMucKeyTransportMessageReceived(muc, senderDevice.getJid(), cipherAndAuthTag, 1324 carbonCopy, wrappingMessage, messageInfo); 1325 } 1326 } 1327 } 1328 // ... or a normal chat message... 1329 else { 1330 if (omemoMessage.isMessageElement()) { 1331 1332 decrypted = service.processReceivingMessage(omemoManager, senderDevice, omemoMessage, messageInfo); 1333 if (decrypted != null) { 1334 omemoManager.notifyOmemoMessageReceived(decrypted.getBody(), carbonCopy, null, messageInfo); 1335 } 1336 1337 } else if (omemoMessage.isKeyTransportElement()) { 1338 1339 CipherAndAuthTag cipherAndAuthTag = decryptTransportedOmemoKey(omemoManager, senderDevice, omemoMessage, messageInfo); 1340 if (cipherAndAuthTag != null) { 1341 omemoManager.notifyOmemoKeyTransportMessageReceived(cipherAndAuthTag, carbonCopy, null, messageInfo); 1342 } 1343 } 1344 } 1345 1346 } catch (CryptoFailedException | CorruptedOmemoKeyException | InterruptedException | SmackException.NotConnectedException | XMPPException.XMPPErrorException | SmackException.NoResponseException e) { 1347 LOGGER.log(Level.WARNING, "internal omemoMessageListener failed to decrypt incoming OMEMO carbon copy: " 1348 + e.getMessage()); 1349 1350 } catch (final NoRawSessionException e) { 1351 Async.go(new Runnable() { 1352 @Override 1353 public void run() { 1354 try { 1355 LOGGER.log(Level.INFO, "Received OMEMO carbon copy message with invalid session from " + 1356 senderDevice + ". Send RatchetUpdateMessage."); 1357 service.sendOmemoRatchetUpdateMessage(omemoManager, senderDevice, true); 1358 1359 } catch (UndecidedOmemoIdentityException | CorruptedOmemoKeyException | CannotEstablishOmemoSessionException | CryptoFailedException e1) { 1360 LOGGER.log(Level.WARNING, "internal omemoMessageListener failed to establish a session for incoming OMEMO carbon message: " 1361 + e.getMessage()); 1362 } 1363 } 1364 }); 1365 1366 } 1367 } 1368 } 1369 } 1370}