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.BODY_OMEMO_HINT; 020import static org.jivesoftware.smackx.omemo.util.OmemoConstants.OMEMO; 021import static org.jivesoftware.smackx.omemo.util.OmemoConstants.OMEMO_NAMESPACE_V_AXOLOTL; 022import static org.jivesoftware.smackx.omemo.util.OmemoConstants.PEP_NODE_DEVICE_LIST_NOTIFY; 023 024import java.security.NoSuchAlgorithmException; 025import java.util.ArrayList; 026import java.util.HashMap; 027import java.util.HashSet; 028import java.util.List; 029import java.util.Random; 030import java.util.Set; 031import java.util.WeakHashMap; 032import java.util.logging.Level; 033import java.util.logging.Logger; 034 035import org.jivesoftware.smack.AbstractConnectionListener; 036import org.jivesoftware.smack.AbstractXMPPConnection; 037import org.jivesoftware.smack.Manager; 038import org.jivesoftware.smack.SmackException; 039import org.jivesoftware.smack.XMPPConnection; 040import org.jivesoftware.smack.XMPPException; 041import org.jivesoftware.smack.packet.ExtensionElement; 042import org.jivesoftware.smack.packet.Message; 043import org.jivesoftware.smack.packet.Stanza; 044import org.jivesoftware.smack.util.Async; 045 046import org.jivesoftware.smackx.carbons.CarbonManager; 047import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; 048import org.jivesoftware.smackx.eme.element.ExplicitMessageEncryptionElement; 049import org.jivesoftware.smackx.hints.element.StoreHint; 050import org.jivesoftware.smackx.mam.MamManager; 051import org.jivesoftware.smackx.muc.MultiUserChat; 052import org.jivesoftware.smackx.muc.MultiUserChatManager; 053import org.jivesoftware.smackx.muc.RoomInfo; 054import org.jivesoftware.smackx.omemo.element.OmemoDeviceListVAxolotlElement; 055import org.jivesoftware.smackx.omemo.element.OmemoElement; 056import org.jivesoftware.smackx.omemo.element.OmemoVAxolotlElement; 057import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException; 058import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException; 059import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException; 060import org.jivesoftware.smackx.omemo.exceptions.NoOmemoSupportException; 061import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException; 062import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException; 063import org.jivesoftware.smackx.omemo.internal.CachedDeviceList; 064import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag; 065import org.jivesoftware.smackx.omemo.internal.ClearTextMessage; 066import org.jivesoftware.smackx.omemo.internal.OmemoDevice; 067import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation; 068import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener; 069import org.jivesoftware.smackx.omemo.listener.OmemoMucMessageListener; 070import org.jivesoftware.smackx.pep.PEPListener; 071import org.jivesoftware.smackx.pep.PEPManager; 072import org.jivesoftware.smackx.pubsub.EventElement; 073import org.jivesoftware.smackx.pubsub.ItemsExtension; 074import org.jivesoftware.smackx.pubsub.PayloadItem; 075import org.jivesoftware.smackx.pubsub.PubSubException; 076import org.jivesoftware.smackx.pubsub.packet.PubSub; 077 078import org.jxmpp.jid.BareJid; 079import org.jxmpp.jid.DomainBareJid; 080import org.jxmpp.jid.EntityBareJid; 081import org.jxmpp.jid.EntityFullJid; 082import org.jxmpp.jid.impl.JidCreate; 083import org.jxmpp.stringprep.XmppStringprepException; 084 085/** 086 * Manager that allows sending messages encrypted with OMEMO. 087 * This class also provides some methods useful for a client that implements OMEMO. 088 * 089 * @author Paul Schaub 090 */ 091 092public final class OmemoManager extends Manager { 093 private static final Logger LOGGER = Logger.getLogger(OmemoManager.class.getName()); 094 095 private static final WeakHashMap<XMPPConnection, WeakHashMap<Integer,OmemoManager>> INSTANCES = new WeakHashMap<>(); 096 private final OmemoService<?, ?, ?, ?, ?, ?, ?, ?, ?> service; 097 098 private final HashSet<OmemoMessageListener> omemoMessageListeners = new HashSet<>(); 099 private final HashSet<OmemoMucMessageListener> omemoMucMessageListeners = new HashSet<>(); 100 101 private OmemoService<?,?,?,?,?,?,?,?,?>.OmemoStanzaListener omemoStanzaListener; 102 private OmemoService<?,?,?,?,?,?,?,?,?>.OmemoCarbonCopyListener omemoCarbonCopyListener; 103 104 private int deviceId; 105 106 /** 107 * Private constructor to prevent multiple instances on a single connection (which probably would be bad!). 108 * 109 * @param connection connection 110 */ 111 private OmemoManager(XMPPConnection connection, int deviceId) { 112 super(connection); 113 114 this.deviceId = deviceId; 115 116 connection.addConnectionListener(new AbstractConnectionListener() { 117 @Override 118 public void authenticated(XMPPConnection connection, boolean resumed) { 119 if (resumed) { 120 return; 121 } 122 Async.go(new Runnable() { 123 @Override 124 public void run() { 125 try { 126 initialize(); 127 } catch (InterruptedException | CorruptedOmemoKeyException | PubSubException.NotALeafNodeException | SmackException.NotLoggedInException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) { 128 LOGGER.log(Level.SEVERE, "connectionListener.authenticated() failed to initialize OmemoManager: " 129 + e.getMessage()); 130 } 131 } 132 }); 133 } 134 }); 135 136 PEPManager.getInstanceFor(connection).addPEPListener(deviceListUpdateListener); 137 ServiceDiscoveryManager.getInstanceFor(connection).addFeature(PEP_NODE_DEVICE_LIST_NOTIFY); 138 139 service = OmemoService.getInstance(); 140 } 141 142 /** 143 * Get an instance of the OmemoManager for the given connection and deviceId. 144 * 145 * @param connection Connection 146 * @param deviceId deviceId of the Manager. If the deviceId is null, a random id will be generated. 147 * @return an OmemoManager 148 */ 149 public synchronized static OmemoManager getInstanceFor(XMPPConnection connection, Integer deviceId) { 150 WeakHashMap<Integer,OmemoManager> managersOfConnection = INSTANCES.get(connection); 151 if (managersOfConnection == null) { 152 managersOfConnection = new WeakHashMap<>(); 153 INSTANCES.put(connection, managersOfConnection); 154 } 155 156 if (deviceId == null || deviceId < 1) { 157 deviceId = randomDeviceId(); 158 } 159 160 OmemoManager manager = managersOfConnection.get(deviceId); 161 if (manager == null) { 162 manager = new OmemoManager(connection, deviceId); 163 managersOfConnection.put(deviceId, manager); 164 } 165 return manager; 166 } 167 168 /** 169 * Get an instance of the OmemoManager for the given connection. 170 * This method creates the OmemoManager for the stored defaultDeviceId of the connections user. 171 * If there is no such id is stored, it uses a fresh deviceId and sets that as defaultDeviceId instead. 172 * 173 * @param connection connection 174 * @return OmemoManager 175 */ 176 public synchronized static OmemoManager getInstanceFor(XMPPConnection connection) { 177 BareJid user; 178 if (connection.getUser() != null) { 179 user = connection.getUser().asBareJid(); 180 } else { 181 // This might be dangerous 182 try { 183 user = JidCreate.bareFrom(((AbstractXMPPConnection) connection).getConfiguration().getUsername()); 184 } catch (XmppStringprepException e) { 185 throw new AssertionError("Username is not a valid Jid. " + 186 "Use OmemoManager.gerInstanceFor(Connection, deviceId) instead."); 187 } 188 } 189 190 int defaultDeviceId = OmemoService.getInstance().getOmemoStoreBackend().getDefaultDeviceId(user); 191 if (defaultDeviceId < 1) { 192 defaultDeviceId = randomDeviceId(); 193 OmemoService.getInstance().getOmemoStoreBackend().setDefaultDeviceId(user, defaultDeviceId); 194 } 195 196 return getInstanceFor(connection, defaultDeviceId); 197 } 198 199 /** 200 * Initializes the OmemoManager. This method is called automatically once the client logs into the server successfully. 201 * 202 * @throws CorruptedOmemoKeyException 203 * @throws InterruptedException 204 * @throws SmackException.NoResponseException 205 * @throws SmackException.NotConnectedException 206 * @throws XMPPException.XMPPErrorException 207 * @throws SmackException.NotLoggedInException 208 * @throws PubSubException.NotALeafNodeException 209 */ 210 public void initialize() throws CorruptedOmemoKeyException, InterruptedException, SmackException.NoResponseException, 211 SmackException.NotConnectedException, XMPPException.XMPPErrorException, SmackException.NotLoggedInException, 212 PubSubException.NotALeafNodeException { 213 getOmemoService().initialize(this); 214 } 215 216 /** 217 * OMEMO encrypt a cleartext message for a single recipient. 218 * 219 * @param to recipients barejid 220 * @param message text to encrypt 221 * @return encrypted message 222 * @throws CryptoFailedException when something crypto related fails 223 * @throws UndecidedOmemoIdentityException When there are undecided devices 224 * @throws NoSuchAlgorithmException 225 * @throws InterruptedException 226 * @throws CannotEstablishOmemoSessionException when we could not create session withs all of the recipients devices. 227 * @throws SmackException.NotConnectedException 228 * @throws SmackException.NoResponseException 229 */ 230 public Message encrypt(BareJid to, String message) throws CryptoFailedException, UndecidedOmemoIdentityException, NoSuchAlgorithmException, InterruptedException, CannotEstablishOmemoSessionException, SmackException.NotConnectedException, SmackException.NoResponseException { 231 Message m = new Message(); 232 m.setBody(message); 233 OmemoVAxolotlElement encrypted = getOmemoService().processSendingMessage(this, to, m); 234 return finishMessage(encrypted); 235 } 236 237 /** 238 * OMEMO encrypt a cleartext message for multiple recipients. 239 * 240 * @param recipients recipients barejids 241 * @param message text to encrypt 242 * @return encrypted message. 243 * @throws CryptoFailedException When something crypto related fails 244 * @throws UndecidedOmemoIdentityException When there are undecided devices. 245 * @throws NoSuchAlgorithmException 246 * @throws InterruptedException 247 * @throws CannotEstablishOmemoSessionException When there is one recipient, for whom we failed to create a session 248 * with every one of their devices. 249 * @throws SmackException.NotConnectedException 250 * @throws SmackException.NoResponseException 251 */ 252 public Message encrypt(ArrayList<BareJid> recipients, String message) throws CryptoFailedException, UndecidedOmemoIdentityException, NoSuchAlgorithmException, InterruptedException, CannotEstablishOmemoSessionException, SmackException.NotConnectedException, SmackException.NoResponseException { 253 Message m = new Message(); 254 m.setBody(message); 255 OmemoVAxolotlElement encrypted = getOmemoService().processSendingMessage(this, recipients, m); 256 return finishMessage(encrypted); 257 } 258 259 /** 260 * Encrypt a message for all recipients in the MultiUserChat. 261 * 262 * @param muc multiUserChat 263 * @param message message to send 264 * @return encrypted message 265 * @throws UndecidedOmemoIdentityException when there are undecided devices. 266 * @throws NoSuchAlgorithmException 267 * @throws CryptoFailedException 268 * @throws XMPPException.XMPPErrorException 269 * @throws SmackException.NotConnectedException 270 * @throws InterruptedException 271 * @throws SmackException.NoResponseException 272 * @throws NoOmemoSupportException When the muc doesn't support OMEMO. 273 * @throws CannotEstablishOmemoSessionException when there is a user for whom we could not create a session 274 * with any of their devices. 275 */ 276 public Message encrypt(MultiUserChat muc, String message) throws UndecidedOmemoIdentityException, NoSuchAlgorithmException, CryptoFailedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, NoOmemoSupportException, CannotEstablishOmemoSessionException { 277 if (!multiUserChatSupportsOmemo(muc.getRoom())) { 278 throw new NoOmemoSupportException(); 279 } 280 Message m = new Message(); 281 m.setBody(message); 282 ArrayList<BareJid> recipients = new ArrayList<>(); 283 for (EntityFullJid e : muc.getOccupants()) { 284 recipients.add(muc.getOccupant(e).getJid().asBareJid()); 285 } 286 return encrypt(recipients, message); 287 } 288 289 /** 290 * Encrypt a message for all users we could build a session with successfully in a previous attempt. 291 * This method can come in handy as a fallback when encrypting a message fails due to devices we cannot 292 * build a session with. 293 * 294 * @param exception CannotEstablishSessionException from a previous encrypt(user(s), message) call. 295 * @param message message we want to send. 296 * @return encrypted message 297 * @throws CryptoFailedException 298 * @throws UndecidedOmemoIdentityException when there are undecided identities. 299 */ 300 public Message encryptForExistingSessions(CannotEstablishOmemoSessionException exception, String message) throws CryptoFailedException, UndecidedOmemoIdentityException { 301 Message m = new Message(); 302 m.setBody(message); 303 OmemoVAxolotlElement encrypted = getOmemoService().encryptOmemoMessage(this, exception.getSuccesses(), m); 304 return finishMessage(encrypted); 305 } 306 307 /** 308 * Decrypt an OMEMO message. This method comes handy when dealing with messages that were not automatically 309 * decrypted by smack-omemo, eg. MAM query messages. 310 * @param sender sender of the message 311 * @param omemoMessage message 312 * @return decrypted message 313 * @throws InterruptedException Exception 314 * @throws SmackException.NoResponseException Exception 315 * @throws SmackException.NotConnectedException Exception 316 * @throws CryptoFailedException When decryption fails 317 * @throws XMPPException.XMPPErrorException Exception 318 * @throws CorruptedOmemoKeyException When the used keys are invalid 319 * @throws NoRawSessionException When there is no double ratchet session found for this message 320 */ 321 public ClearTextMessage decrypt(BareJid sender, Message omemoMessage) throws InterruptedException, SmackException.NoResponseException, SmackException.NotConnectedException, CryptoFailedException, XMPPException.XMPPErrorException, CorruptedOmemoKeyException, NoRawSessionException { 322 return getOmemoService().processLocalMessage(this, sender, omemoMessage); 323 } 324 325 /** 326 * Return a list of all OMEMO messages that were found in the MAM query result, that could be successfully decrypted. 327 * Normal cleartext messages are also added to this list. 328 * 329 * @param mamQueryResult mamQueryResult 330 * @return list of decrypted OmemoMessages 331 * @throws InterruptedException Exception 332 * @throws XMPPException.XMPPErrorException Exception 333 * @throws SmackException.NotConnectedException Exception 334 * @throws SmackException.NoResponseException Exception 335 */ 336 public List<ClearTextMessage> decryptMamQueryResult(MamManager.MamQueryResult mamQueryResult) throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException { 337 List<ClearTextMessage> l = new ArrayList<>(); 338 l.addAll(getOmemoService().decryptMamQueryResult(this, mamQueryResult)); 339 return l; 340 } 341 342 /** 343 * Trust that a fingerprint belongs to an OmemoDevice. 344 * The fingerprint must be the lowercase, hexadecimal fingerprint of the identityKey of the device and must 345 * be of length 64. 346 * @param device device 347 * @param fingerprint fingerprint 348 */ 349 public void trustOmemoIdentity(OmemoDevice device, OmemoFingerprint fingerprint) { 350 getOmemoService().getOmemoStoreBackend().trustOmemoIdentity(this, device, fingerprint); 351 } 352 353 /** 354 * Distrust the fingerprint/OmemoDevice tuple. 355 * The fingerprint must be the lowercase, hexadecimal fingerprint of the identityKey of the device and must 356 * be of length 64. 357 * @param device device 358 * @param fingerprint fingerprint 359 */ 360 public void distrustOmemoIdentity(OmemoDevice device, OmemoFingerprint fingerprint) { 361 getOmemoService().getOmemoStoreBackend().distrustOmemoIdentity(this, device, fingerprint); 362 } 363 364 /** 365 * Returns true, if the fingerprint/OmemoDevice tuple is trusted, otherwise false. 366 * The fingerprint must be the lowercase, hexadecimal fingerprint of the identityKey of the device and must 367 * be of length 64. 368 * @param device device 369 * @param fingerprint fingerprint 370 * @return 371 */ 372 public boolean isTrustedOmemoIdentity(OmemoDevice device, OmemoFingerprint fingerprint) { 373 return getOmemoService().getOmemoStoreBackend().isTrustedOmemoIdentity(this, device, fingerprint); 374 } 375 376 /** 377 * Returns true, if the fingerprint/OmemoDevice tuple is decided by the user. 378 * The fingerprint must be the lowercase, hexadecimal fingerprint of the identityKey of the device and must 379 * be of length 64. 380 * @param device device 381 * @param fingerprint fingerprint 382 * @return 383 */ 384 public boolean isDecidedOmemoIdentity(OmemoDevice device, OmemoFingerprint fingerprint) { 385 return getOmemoService().getOmemoStoreBackend().isDecidedOmemoIdentity(this, device, fingerprint); 386 } 387 388 /** 389 * Clear all other devices except this one from our device list and republish the list. 390 * 391 * @throws InterruptedException 392 * @throws SmackException 393 * @throws XMPPException.XMPPErrorException 394 * @throws CorruptedOmemoKeyException 395 */ 396 public void purgeDevices() throws SmackException, InterruptedException, XMPPException.XMPPErrorException, CorruptedOmemoKeyException { 397 getOmemoService().publishDeviceIdIfNeeded(this,true); 398 getOmemoService().publishBundle(this); 399 } 400 401 /** 402 * Generate fresh identity keys and bundle and publish it to the server. 403 * @throws SmackException 404 * @throws InterruptedException 405 * @throws XMPPException.XMPPErrorException 406 * @throws CorruptedOmemoKeyException 407 */ 408 public void regenerate() throws SmackException, InterruptedException, XMPPException.XMPPErrorException, CorruptedOmemoKeyException { 409 // create a new identity and publish new keys to the server 410 getOmemoService().regenerate(this, null); 411 getOmemoService().publishDeviceIdIfNeeded(this,false); 412 getOmemoService().publishBundle(this); 413 } 414 415 /** 416 * Send a ratchet update message. This can be used to advance the ratchet of a session in order to maintain forward 417 * secrecy. 418 * 419 * @param recipient recipient 420 * @throws UndecidedOmemoIdentityException When the trust of session with the recipient is not decided yet 421 * @throws CorruptedOmemoKeyException When the used identityKeys are corrupted 422 * @throws CryptoFailedException When something fails with the crypto 423 * @throws CannotEstablishOmemoSessionException When we can't establish a session with the recipient 424 */ 425 public void sendRatchetUpdateMessage(OmemoDevice recipient) 426 throws CorruptedOmemoKeyException, UndecidedOmemoIdentityException, CryptoFailedException, 427 CannotEstablishOmemoSessionException { 428 getOmemoService().sendOmemoRatchetUpdateMessage(this, recipient, false); 429 } 430 431 /** 432 * Create a new KeyTransportElement. This message will contain the AES-Key and IV that can be used eg. for encrypted 433 * Jingle file transfer. 434 * 435 * @param aesKey AES key to transport 436 * @param iv Initialization vector 437 * @param to list of recipient devices 438 * @return KeyTransportMessage 439 * @throws UndecidedOmemoIdentityException When the trust of session with the recipient is not decided yet 440 * @throws CorruptedOmemoKeyException When the used identityKeys are corrupted 441 * @throws CryptoFailedException When something fails with the crypto 442 * @throws CannotEstablishOmemoSessionException When we can't establish a session with the recipient 443 */ 444 public OmemoVAxolotlElement createKeyTransportElement(byte[] aesKey, byte[] iv, OmemoDevice ... to) 445 throws UndecidedOmemoIdentityException, CorruptedOmemoKeyException, CryptoFailedException, 446 CannotEstablishOmemoSessionException { 447 return getOmemoService().prepareOmemoKeyTransportElement(this, aesKey, iv, to); 448 } 449 450 /** 451 * Create a new Message from a encrypted OmemoMessageElement. 452 * Add ourselves as the sender and the encrypted element. 453 * Also tell the server to store the message despite a possible missing body. 454 * The body will be set to a hint message that we are using OMEMO. 455 * 456 * @param encrypted OmemoMessageElement 457 * @return Message containing the OMEMO element and some additional information 458 */ 459 Message finishMessage(OmemoVAxolotlElement encrypted) { 460 if (encrypted == null) { 461 return null; 462 } 463 464 Message chatMessage = new Message(); 465 chatMessage.setFrom(connection().getUser().asBareJid()); 466 chatMessage.addExtension(encrypted); 467 468 if (OmemoConfiguration.getAddOmemoHintBody()) { 469 chatMessage.setBody(BODY_OMEMO_HINT); 470 } 471 472 if (OmemoConfiguration.getAddMAMStorageProcessingHint()) { 473 StoreHint.set(chatMessage); 474 } 475 476 if (OmemoConfiguration.getAddEmeEncryptionHint()) { 477 chatMessage.addExtension(new ExplicitMessageEncryptionElement(OMEMO_NAMESPACE_V_AXOLOTL, OMEMO)); 478 } 479 480 return chatMessage; 481 } 482 483 /** 484 * Returns true, if the contact has any active devices published in a deviceList. 485 * 486 * @param contact contact 487 * @return true if contact has at least one OMEMO capable device. 488 * @throws SmackException.NotConnectedException 489 * @throws InterruptedException 490 * @throws SmackException.NoResponseException 491 */ 492 public boolean contactSupportsOmemo(BareJid contact) throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { 493 getOmemoService().refreshDeviceList(this, contact); 494 return !getOmemoService().getOmemoStoreBackend().loadCachedDeviceList(this, contact) 495 .getActiveDevices().isEmpty(); 496 } 497 498 /** 499 * Returns true, if the MUC with the EntityBareJid multiUserChat is non-anonymous and members only (prerequisite 500 * for OMEMO encryption in MUC). 501 * 502 * @param multiUserChat EntityBareJid of the MUC 503 * @return true if chat supports OMEMO 504 * @throws XMPPException.XMPPErrorException if 505 * @throws SmackException.NotConnectedException something 506 * @throws InterruptedException goes 507 * @throws SmackException.NoResponseException wrong 508 */ 509 public boolean multiUserChatSupportsOmemo(EntityBareJid multiUserChat) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { 510 RoomInfo roomInfo = MultiUserChatManager.getInstanceFor(connection()).getRoomInfo(multiUserChat); 511 return roomInfo.isNonanonymous() && roomInfo.isMembersOnly(); 512 } 513 514 /** 515 * Returns true, if the Server supports PEP. 516 * 517 * @param connection XMPPConnection 518 * @param server domainBareJid of the server to test 519 * @return true if server supports pep 520 * @throws XMPPException.XMPPErrorException 521 * @throws SmackException.NotConnectedException 522 * @throws InterruptedException 523 * @throws SmackException.NoResponseException 524 */ 525 public static boolean serverSupportsOmemo(XMPPConnection connection, DomainBareJid server) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { 526 return ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(server).containsFeature(PubSub.NAMESPACE); 527 } 528 529 /** 530 * Return the fingerprint of our identity key. 531 * 532 * @return fingerprint 533 */ 534 public OmemoFingerprint getOurFingerprint() { 535 return getOmemoService().getOmemoStoreBackend().getFingerprint(this); 536 } 537 538 public OmemoFingerprint getFingerprint(OmemoDevice device) throws CannotEstablishOmemoSessionException { 539 if (device.equals(getOwnDevice())) { 540 return getOurFingerprint(); 541 } 542 543 return getOmemoService().getOmemoStoreBackend().getFingerprint(this, device); 544 } 545 546 /** 547 * Return all fingerprints of active devices of a contact. 548 * @param contact contact 549 * @return HashMap of deviceIds and corresponding fingerprints. 550 */ 551 public HashMap<OmemoDevice, OmemoFingerprint> getActiveFingerprints(BareJid contact) { 552 HashMap<OmemoDevice, OmemoFingerprint> fingerprints = new HashMap<>(); 553 CachedDeviceList deviceList = getOmemoService().getOmemoStoreBackend().loadCachedDeviceList(this, contact); 554 for (int id : deviceList.getActiveDevices()) { 555 OmemoDevice device = new OmemoDevice(contact, id); 556 OmemoFingerprint fingerprint = null; 557 try { 558 fingerprint = getFingerprint(device); 559 } catch (CannotEstablishOmemoSessionException e) { 560 LOGGER.log(Level.WARNING, "Could not build session with device " + id 561 + " of user " + contact + ": " + e.getMessage()); 562 } 563 564 if (fingerprint != null) { 565 fingerprints.put(device, fingerprint); 566 } 567 } 568 return fingerprints; 569 } 570 571 public void addOmemoMessageListener(OmemoMessageListener listener) { 572 omemoMessageListeners.add(listener); 573 } 574 575 public void removeOmemoMessageListener(OmemoMessageListener listener) { 576 omemoMessageListeners.remove(listener); 577 } 578 579 public void addOmemoMucMessageListener(OmemoMucMessageListener listener) { 580 omemoMucMessageListeners.add(listener); 581 } 582 583 public void removeOmemoMucMessageListener(OmemoMucMessageListener listener) { 584 omemoMucMessageListeners.remove(listener); 585 } 586 587 /** 588 * Build OMEMO sessions with devices of contact. 589 * 590 * @param contact contact we want to build session with. 591 * @throws InterruptedException 592 * @throws CannotEstablishOmemoSessionException 593 * @throws SmackException.NotConnectedException 594 * @throws SmackException.NoResponseException 595 */ 596 public void buildSessionsWith(BareJid contact) throws InterruptedException, CannotEstablishOmemoSessionException, SmackException.NotConnectedException, SmackException.NoResponseException { 597 getOmemoService().buildOrCreateOmemoSessionsFromBundles(this, contact); 598 } 599 600 /** 601 * Request a deviceList update from contact contact. 602 * 603 * @param contact contact we want to obtain the deviceList from. 604 * @throws SmackException.NotConnectedException 605 * @throws InterruptedException 606 * @throws SmackException.NoResponseException 607 */ 608 public void requestDeviceListUpdateFor(BareJid contact) throws SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { 609 getOmemoService().refreshDeviceList(this, contact); 610 } 611 612 /** 613 * Rotate the signedPreKey published in our OmemoBundle. This should be done every now and then (7-14 days). 614 * The old signedPreKey should be kept for some more time (a month or so) to enable decryption of messages 615 * that have been sent since the key was changed. 616 * 617 * @throws CorruptedOmemoKeyException When the IdentityKeyPair is damaged. 618 * @throws InterruptedException XMPP error 619 * @throws XMPPException.XMPPErrorException XMPP error 620 * @throws SmackException.NotConnectedException XMPP error 621 * @throws SmackException.NoResponseException XMPP error 622 * @throws PubSubException.NotALeafNodeException if the bundle node on the server is a CollectionNode 623 */ 624 public void rotateSignedPreKey() throws CorruptedOmemoKeyException, InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException, PubSubException.NotALeafNodeException { 625 // generate key 626 getOmemoService().getOmemoStoreBackend().changeSignedPreKey(this); 627 // publish 628 getOmemoService().publishDeviceIdIfNeeded(this, false); 629 getOmemoService().publishBundle(this); 630 } 631 632 /** 633 * Return true, if the given Stanza contains an OMEMO element 'encrypted'. 634 * @param stanza stanza 635 * @return true if stanza has extension 'encrypted' 636 */ 637 public static boolean stanzaContainsOmemoElement(Stanza stanza) { 638 return stanza.hasExtension(OmemoElement.ENCRYPTED, OMEMO_NAMESPACE_V_AXOLOTL); 639 } 640 641 /** 642 * Throw an IllegalStateException if no OmemoService is set. 643 */ 644 private void throwIfNoServiceSet() { 645 if (service == null) { 646 throw new IllegalStateException("No OmemoService set in OmemoManager."); 647 } 648 } 649 650 public static int randomDeviceId() { 651 int i = new Random().nextInt(Integer.MAX_VALUE); 652 653 if (i == 0) { 654 return randomDeviceId(); 655 } 656 657 return Math.abs(i); 658 } 659 660 /** 661 * Return the BareJid of the user. 662 * 663 * @return bareJid 664 */ 665 public BareJid getOwnJid() { 666 EntityFullJid fullJid = connection().getUser(); 667 if (fullJid == null) return null; 668 return fullJid.asBareJid(); 669 } 670 671 /** 672 * Return the deviceId of this OmemoManager. 673 * 674 * @return deviceId 675 */ 676 public int getDeviceId() { 677 return deviceId; 678 } 679 680 /** 681 * Return the OmemoDevice of the user. 682 * 683 * @return omemoDevice 684 */ 685 public OmemoDevice getOwnDevice() { 686 return new OmemoDevice(getOwnJid(), getDeviceId()); 687 } 688 689 void setDeviceId(int nDeviceId) { 690 INSTANCES.get(connection()).remove(getDeviceId()); 691 INSTANCES.get(connection()).put(nDeviceId, this); 692 this.deviceId = nDeviceId; 693 } 694 695 /** 696 * Notify all registered OmemoMessageListeners about a received OmemoMessage. 697 * 698 * @param decryptedBody decrypted Body element of the message 699 * @param encryptedMessage unmodified message as it was received 700 * @param wrappingMessage message that wrapped the incoming message 701 * @param messageInformation information about the messages encryption (used identityKey, carbon...) 702 */ 703 void notifyOmemoMessageReceived(String decryptedBody, Message encryptedMessage, Message wrappingMessage, OmemoMessageInformation messageInformation) { 704 for (OmemoMessageListener l : omemoMessageListeners) { 705 l.onOmemoMessageReceived(decryptedBody, encryptedMessage, wrappingMessage, messageInformation); 706 } 707 } 708 709 void notifyOmemoKeyTransportMessageReceived(CipherAndAuthTag cipherAndAuthTag, Message transportingMessage, 710 Message wrappingMessage, OmemoMessageInformation information) { 711 for (OmemoMessageListener l : omemoMessageListeners) { 712 l.onOmemoKeyTransportReceived(cipherAndAuthTag, transportingMessage, wrappingMessage, information); 713 } 714 } 715 716 /** 717 * Notify all registered OmemoMucMessageListeners of an incoming OmemoMessageElement in a MUC. 718 * 719 * @param muc MultiUserChat the message was received in 720 * @param from BareJid of the user that sent the message 721 * @param decryptedBody decrypted body 722 * @param message original message with encrypted content 723 * @param wrappingMessage wrapping message (in case of carbon copy) 724 * @param omemoInformation information about the encryption of the message 725 */ 726 void notifyOmemoMucMessageReceived(MultiUserChat muc, BareJid from, String decryptedBody, Message message, 727 Message wrappingMessage, OmemoMessageInformation omemoInformation) { 728 for (OmemoMucMessageListener l : omemoMucMessageListeners) { 729 l.onOmemoMucMessageReceived(muc, from, decryptedBody, message, 730 wrappingMessage, omemoInformation); 731 } 732 } 733 734 void notifyOmemoMucKeyTransportMessageReceived(MultiUserChat muc, BareJid from, CipherAndAuthTag cipherAndAuthTag, 735 Message transportingMessage, Message wrappingMessage, 736 OmemoMessageInformation messageInformation) { 737 for (OmemoMucMessageListener l : omemoMucMessageListeners) { 738 l.onOmemoKeyTransportReceived(muc, from, cipherAndAuthTag, 739 transportingMessage, wrappingMessage, messageInformation); 740 } 741 } 742 743 /** 744 * Remove all active stanza listeners of this manager from the connection. 745 * This is somewhat the counterpart of initialize(). 746 */ 747 public void shutdown() { 748 PEPManager.getInstanceFor(connection()).removePEPListener(deviceListUpdateListener); 749 connection().removeAsyncStanzaListener(omemoStanzaListener); 750 CarbonManager.getInstanceFor(connection()).removeCarbonCopyReceivedListener(omemoCarbonCopyListener); 751 } 752 753 /** 754 * Get our connection. 755 * 756 * @return the connection of this manager 757 */ 758 XMPPConnection getConnection() { 759 return connection(); 760 } 761 762 /** 763 * Return the OMEMO service object. 764 * 765 * @return omemoService 766 */ 767 OmemoService<?,?,?,?,?,?,?,?,?> getOmemoService() { 768 throwIfNoServiceSet(); 769 return service; 770 } 771 772 PEPListener deviceListUpdateListener = new PEPListener() { 773 @Override 774 public void eventReceived(EntityBareJid from, EventElement event, Message message) { 775 for (ExtensionElement items : event.getExtensions()) { 776 if (!(items instanceof ItemsExtension)) { 777 continue; 778 } 779 780 for (ExtensionElement item : ((ItemsExtension) items).getItems()) { 781 if (!(item instanceof PayloadItem<?>)) { 782 continue; 783 } 784 785 PayloadItem<?> payloadItem = (PayloadItem<?>) item; 786 787 if (!(payloadItem.getPayload() instanceof OmemoDeviceListVAxolotlElement)) { 788 continue; 789 } 790 791 // Device List <list> 792 OmemoDeviceListVAxolotlElement omemoDeviceListElement = (OmemoDeviceListVAxolotlElement) payloadItem.getPayload(); 793 int ourDeviceId = getDeviceId(); 794 getOmemoService().getOmemoStoreBackend().mergeCachedDeviceList(OmemoManager.this, from, omemoDeviceListElement); 795 796 if (from == null) { 797 // Unknown sender, no more work to do. 798 // TODO: This DOES happen for some reason. Figure out when... 799 continue; 800 } 801 802 if (!from.equals(getOwnJid())) { 803 // Not our deviceList, so nothing more to do 804 continue; 805 } 806 807 if (omemoDeviceListElement.getDeviceIds().contains(ourDeviceId)) { 808 // We are on the list. Nothing more to do 809 continue; 810 } 811 812 // Our deviceList and we are not on it! We don't want to miss all the action!!! 813 LOGGER.log(Level.INFO, "Our deviceId was not on the list!"); 814 Set<Integer> deviceListIds = omemoDeviceListElement.copyDeviceIds(); 815 // enroll at the deviceList 816 deviceListIds.add(ourDeviceId); 817 final OmemoDeviceListVAxolotlElement newOmemoDeviceListElement = new OmemoDeviceListVAxolotlElement(deviceListIds); 818 819 // PEPListener is a synchronous listener. Avoid any deadlocks by using an async task to update the device list. 820 Async.go(new Runnable() { 821 @Override 822 public void run() { 823 try { 824 OmemoService.publishDeviceIds(OmemoManager.this, newOmemoDeviceListElement); 825 } 826 catch (SmackException | InterruptedException | XMPPException.XMPPErrorException e) { 827 // TODO: It might be dangerous NOT to retry publishing our deviceId 828 LOGGER.log(Level.SEVERE, 829 "Could not publish our device list after an update without our id was received: " 830 + e.getMessage()); 831 } 832 } 833 }); 834 } 835 } 836 } 837 }; 838 839 840 841 OmemoService<?,?,?,?,?,?,?,?,?>.OmemoStanzaListener getOmemoStanzaListener() { 842 if (omemoStanzaListener == null) { 843 omemoStanzaListener = getOmemoService().createStanzaListener(this); 844 } 845 return omemoStanzaListener; 846 } 847 848 OmemoService<?,?,?,?,?,?,?,?,?>.OmemoCarbonCopyListener getOmemoCarbonCopyListener() { 849 if (omemoCarbonCopyListener == null) { 850 omemoCarbonCopyListener = getOmemoService().createOmemoCarbonCopyListener(this); 851 } 852 return omemoCarbonCopyListener; 853 } 854}