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