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