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_NAMESPACE_V_AXOLOTL; 021import static org.jivesoftware.smackx.omemo.util.OmemoConstants.PEP_NODE_DEVICE_LIST_NOTIFY; 022 023import java.security.NoSuchAlgorithmException; 024import java.util.ArrayList; 025import java.util.HashMap; 026import java.util.HashSet; 027import java.util.List; 028import java.util.Random; 029import java.util.Set; 030import java.util.WeakHashMap; 031import java.util.logging.Level; 032import java.util.logging.Logger; 033 034import org.jivesoftware.smack.AbstractConnectionListener; 035import org.jivesoftware.smack.AbstractXMPPConnection; 036import org.jivesoftware.smack.Manager; 037import org.jivesoftware.smack.SmackException; 038import org.jivesoftware.smack.XMPPConnection; 039import org.jivesoftware.smack.XMPPException; 040import org.jivesoftware.smack.packet.ExtensionElement; 041import org.jivesoftware.smack.packet.Message; 042import org.jivesoftware.smack.packet.NamedElement; 043import org.jivesoftware.smack.packet.Stanza; 044import org.jivesoftware.smack.util.Async; 045import org.jivesoftware.smackx.carbons.CarbonManager; 046import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; 047import org.jivesoftware.smackx.eme.element.ExplicitMessageEncryptionElement; 048import org.jivesoftware.smackx.hints.element.StoreHint; 049import org.jivesoftware.smackx.mam.MamManager; 050import org.jivesoftware.smackx.muc.MultiUserChat; 051import org.jivesoftware.smackx.muc.MultiUserChatManager; 052import org.jivesoftware.smackx.muc.RoomInfo; 053import org.jivesoftware.smackx.omemo.element.OmemoDeviceListVAxolotlElement; 054import org.jivesoftware.smackx.omemo.element.OmemoElement; 055import org.jivesoftware.smackx.omemo.element.OmemoVAxolotlElement; 056import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException; 057import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException; 058import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException; 059import org.jivesoftware.smackx.omemo.exceptions.NoOmemoSupportException; 060import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException; 061import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException; 062import org.jivesoftware.smackx.omemo.internal.CachedDeviceList; 063import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag; 064import org.jivesoftware.smackx.omemo.internal.ClearTextMessage; 065import org.jivesoftware.smackx.omemo.internal.OmemoDevice; 066import org.jivesoftware.smackx.omemo.internal.OmemoMessageInformation; 067import org.jivesoftware.smackx.omemo.listener.OmemoMessageListener; 068import org.jivesoftware.smackx.omemo.listener.OmemoMucMessageListener; 069import org.jivesoftware.smackx.pep.PEPListener; 070import org.jivesoftware.smackx.pep.PEPManager; 071import org.jivesoftware.smackx.pubsub.EventElement; 072import org.jivesoftware.smackx.pubsub.ItemsExtension; 073import org.jivesoftware.smackx.pubsub.PayloadItem; 074import org.jivesoftware.smackx.pubsub.PubSubException; 075import org.jivesoftware.smackx.pubsub.packet.PubSub; 076 077import org.jxmpp.jid.BareJid; 078import org.jxmpp.jid.DomainBareJid; 079import org.jxmpp.jid.EntityBareJid; 080import org.jxmpp.jid.EntityFullJid; 081import org.jxmpp.jid.impl.JidCreate; 082import org.jxmpp.stringprep.XmppStringprepException; 083 084/** 085 * Manager that allows sending messages encrypted with OMEMO. 086 * This class also provides some methods useful for a client that implements OMEMO. 087 * 088 * @author Paul Schaub 089 */ 090 091public final class OmemoManager extends Manager { 092 private static final Logger LOGGER = Logger.getLogger(OmemoManager.class.getName()); 093 094 private static final WeakHashMap<XMPPConnection, WeakHashMap<Integer,OmemoManager>> INSTANCES = new WeakHashMap<>(); 095 private final OmemoService<?, ?, ?, ?, ?, ?, ?, ?, ?> service; 096 097 private final HashSet<OmemoMessageListener> omemoMessageListeners = new HashSet<>(); 098 private final HashSet<OmemoMucMessageListener> omemoMucMessageListeners = new HashSet<>(); 099 100 private OmemoService<?,?,?,?,?,?,?,?,?>.OmemoStanzaListener omemoStanzaListener; 101 private OmemoService<?,?,?,?,?,?,?,?,?>.OmemoCarbonCopyListener omemoCarbonCopyListener; 102 103 private int deviceId; 104 105 /** 106 * Private constructor to prevent multiple instances on a single connection (which probably would be bad!). 107 * 108 * @param connection connection 109 */ 110 private OmemoManager(XMPPConnection connection, int deviceId) { 111 super(connection); 112 113 this.deviceId = deviceId; 114 115 connection.addConnectionListener(new AbstractConnectionListener() { 116 @Override 117 public void authenticated(XMPPConnection connection, boolean resumed) { 118 if (resumed) { 119 return; 120 } 121 Async.go(new Runnable() { 122 @Override 123 public void run() { 124 try { 125 initialize(); 126 } catch (InterruptedException | CorruptedOmemoKeyException | PubSubException.NotALeafNodeException | SmackException.NotLoggedInException | SmackException.NoResponseException | SmackException.NotConnectedException | XMPPException.XMPPErrorException e) { 127 LOGGER.log(Level.SEVERE, "connectionListener.authenticated() failed to initialize OmemoManager: " 128 + e.getMessage()); 129 } 130 } 131 }); 132 } 133 }); 134 135 PEPManager.getInstanceFor(connection).addPEPListener(deviceListUpdateListener); 136 ServiceDiscoveryManager.getInstanceFor(connection).addFeature(PEP_NODE_DEVICE_LIST_NOTIFY); 137 138 service = OmemoService.getInstance(); 139 } 140 141 /** 142 * Get an instance of the OmemoManager for the given connection and deviceId. 143 * 144 * @param connection Connection 145 * @param deviceId deviceId of the Manager. If the deviceId is null, a random id will be generated. 146 * @return an OmemoManager 147 */ 148 public static synchronized OmemoManager getInstanceFor(XMPPConnection connection, Integer deviceId) { 149 WeakHashMap<Integer,OmemoManager> managersOfConnection = INSTANCES.get(connection); 150 if (managersOfConnection == null) { 151 managersOfConnection = new WeakHashMap<>(); 152 INSTANCES.put(connection, managersOfConnection); 153 } 154 155 if (deviceId == null || deviceId < 1) { 156 deviceId = randomDeviceId(); 157 } 158 159 OmemoManager manager = managersOfConnection.get(deviceId); 160 if (manager == null) { 161 manager = new OmemoManager(connection, deviceId); 162 managersOfConnection.put(deviceId, manager); 163 } 164 return manager; 165 } 166 167 /** 168 * Get an instance of the OmemoManager for the given connection. 169 * This method creates the OmemoManager for the stored defaultDeviceId of the connections user. 170 * If there is no such id is stored, it uses a fresh deviceId and sets that as defaultDeviceId instead. 171 * 172 * @param connection connection 173 * @return OmemoManager 174 */ 175 public static synchronized OmemoManager getInstanceFor(XMPPConnection connection) { 176 BareJid user; 177 if (connection.getUser() != null) { 178 user = connection.getUser().asBareJid(); 179 } else { 180 // This might be dangerous 181 try { 182 user = JidCreate.bareFrom(((AbstractXMPPConnection) connection).getConfiguration().getUsername()); 183 } catch (XmppStringprepException e) { 184 throw new AssertionError("Username is not a valid Jid. " + 185 "Use OmemoManager.gerInstanceFor(Connection, deviceId) instead."); 186 } 187 } 188 189 int defaultDeviceId = OmemoService.getInstance().getOmemoStoreBackend().getDefaultDeviceId(user); 190 if (defaultDeviceId < 1) { 191 defaultDeviceId = randomDeviceId(); 192 OmemoService.getInstance().getOmemoStoreBackend().setDefaultDeviceId(user, defaultDeviceId); 193 } 194 195 return getInstanceFor(connection, defaultDeviceId); 196 } 197 198 /** 199 * Initializes the OmemoManager. This method is called automatically once the client logs into the server successfully. 200 * 201 * @throws CorruptedOmemoKeyException 202 * @throws InterruptedException 203 * @throws SmackException.NoResponseException 204 * @throws SmackException.NotConnectedException 205 * @throws XMPPException.XMPPErrorException 206 * @throws SmackException.NotLoggedInException 207 * @throws PubSubException.NotALeafNodeException 208 */ 209 public void initialize() throws CorruptedOmemoKeyException, InterruptedException, SmackException.NoResponseException, 210 SmackException.NotConnectedException, XMPPException.XMPPErrorException, SmackException.NotLoggedInException, 211 PubSubException.NotALeafNodeException { 212 getOmemoService().initialize(this); 213 } 214 215 /** 216 * OMEMO encrypt a cleartext message for a single recipient. 217 * 218 * @param to recipients barejid 219 * @param message text to encrypt 220 * @return encrypted message 221 * @throws CryptoFailedException when something crypto related fails 222 * @throws UndecidedOmemoIdentityException When there are undecided devices 223 * @throws NoSuchAlgorithmException 224 * @throws InterruptedException 225 * @throws CannotEstablishOmemoSessionException when we could not create session withs all of the recipients devices. 226 * @throws SmackException.NotConnectedException 227 * @throws SmackException.NoResponseException 228 */ 229 public Message encrypt(BareJid to, String message) throws CryptoFailedException, UndecidedOmemoIdentityException, NoSuchAlgorithmException, InterruptedException, CannotEstablishOmemoSessionException, SmackException.NotConnectedException, SmackException.NoResponseException { 230 Message m = new Message(); 231 m.setBody(message); 232 OmemoVAxolotlElement encrypted = getOmemoService().processSendingMessage(this, to, m); 233 return finishMessage(encrypted); 234 } 235 236 /** 237 * OMEMO encrypt a cleartext message for multiple recipients. 238 * 239 * @param recipients recipients barejids 240 * @param message text to encrypt 241 * @return encrypted message. 242 * @throws CryptoFailedException When something crypto related fails 243 * @throws UndecidedOmemoIdentityException When there are undecided devices. 244 * @throws NoSuchAlgorithmException 245 * @throws InterruptedException 246 * @throws CannotEstablishOmemoSessionException When there is one recipient, for whom we failed to create a session 247 * with every one of their devices. 248 * @throws SmackException.NotConnectedException 249 * @throws SmackException.NoResponseException 250 */ 251 public Message encrypt(ArrayList<BareJid> recipients, String message) throws CryptoFailedException, UndecidedOmemoIdentityException, NoSuchAlgorithmException, InterruptedException, CannotEstablishOmemoSessionException, SmackException.NotConnectedException, SmackException.NoResponseException { 252 Message m = new Message(); 253 m.setBody(message); 254 OmemoVAxolotlElement encrypted = getOmemoService().processSendingMessage(this, recipients, m); 255 return finishMessage(encrypted); 256 } 257 258 /** 259 * Encrypt a message for all recipients in the MultiUserChat. 260 * 261 * @param muc multiUserChat 262 * @param message message to send 263 * @return encrypted message 264 * @throws UndecidedOmemoIdentityException when there are undecided devices. 265 * @throws NoSuchAlgorithmException 266 * @throws CryptoFailedException 267 * @throws XMPPException.XMPPErrorException 268 * @throws SmackException.NotConnectedException 269 * @throws InterruptedException 270 * @throws SmackException.NoResponseException 271 * @throws NoOmemoSupportException When the muc doesn't support OMEMO. 272 * @throws CannotEstablishOmemoSessionException when there is a user for whom we could not create a session 273 * with any of their devices. 274 */ 275 public Message encrypt(MultiUserChat muc, String message) throws UndecidedOmemoIdentityException, NoSuchAlgorithmException, CryptoFailedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, NoOmemoSupportException, CannotEstablishOmemoSessionException { 276 if (!multiUserChatSupportsOmemo(muc.getRoom())) { 277 throw new NoOmemoSupportException(); 278 } 279 Message m = new Message(); 280 m.setBody(message); 281 ArrayList<BareJid> recipients = new ArrayList<>(); 282 for (EntityFullJid e : muc.getOccupants()) { 283 recipients.add(muc.getOccupant(e).getJid().asBareJid()); 284 } 285 return encrypt(recipients, message); 286 } 287 288 /** 289 * Encrypt a message for all users we could build a session with successfully in a previous attempt. 290 * This method can come in handy as a fallback when encrypting a message fails due to devices we cannot 291 * build a session with. 292 * 293 * @param exception CannotEstablishSessionException from a previous encrypt(user(s), message) call. 294 * @param message message we want to send. 295 * @return encrypted message 296 * @throws CryptoFailedException 297 * @throws UndecidedOmemoIdentityException when there are undecided identities. 298 */ 299 public Message encryptForExistingSessions(CannotEstablishOmemoSessionException exception, String message) throws CryptoFailedException, UndecidedOmemoIdentityException { 300 Message m = new Message(); 301 m.setBody(message); 302 OmemoVAxolotlElement encrypted = getOmemoService().encryptOmemoMessage(this, exception.getSuccesses(), m); 303 return finishMessage(encrypted); 304 } 305 306 /** 307 * Decrypt an OMEMO message. This method comes handy when dealing with messages that were not automatically 308 * decrypted by smack-omemo, eg. MAM query messages. 309 * @param sender sender of the message 310 * @param omemoMessage message 311 * @return decrypted message 312 * @throws InterruptedException Exception 313 * @throws SmackException.NoResponseException Exception 314 * @throws SmackException.NotConnectedException Exception 315 * @throws CryptoFailedException When decryption fails 316 * @throws XMPPException.XMPPErrorException Exception 317 * @throws CorruptedOmemoKeyException When the used keys are invalid 318 * @throws NoRawSessionException When there is no double ratchet session found for this message 319 */ 320 public ClearTextMessage decrypt(BareJid sender, Message omemoMessage) throws InterruptedException, SmackException.NoResponseException, SmackException.NotConnectedException, CryptoFailedException, XMPPException.XMPPErrorException, CorruptedOmemoKeyException, NoRawSessionException { 321 return getOmemoService().processLocalMessage(this, sender, omemoMessage); 322 } 323 324 /** 325 * Return a list of all OMEMO messages that were found in the MAM query result, that could be successfully decrypted. 326 * Normal cleartext messages are also added to this list. 327 * 328 * @param mamQuery The MAM query 329 * @return list of decrypted OmemoMessages 330 * @throws InterruptedException Exception 331 * @throws XMPPException.XMPPErrorException Exception 332 * @throws SmackException.NotConnectedException Exception 333 * @throws SmackException.NoResponseException Exception 334 */ 335 public List<ClearTextMessage> decryptMamQueryResult(MamManager.MamQuery mamQuery) throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException { 336 List<ClearTextMessage> l = new ArrayList<>(); 337 l.addAll(getOmemoService().decryptMamQueryResult(this, mamQuery)); 338 return l; 339 } 340 341 /** 342 * Trust that a fingerprint belongs to an OmemoDevice. 343 * The fingerprint must be the lowercase, hexadecimal fingerprint of the identityKey of the device and must 344 * be of length 64. 345 * @param device device 346 * @param fingerprint fingerprint 347 */ 348 public void trustOmemoIdentity(OmemoDevice device, OmemoFingerprint fingerprint) { 349 getOmemoService().getOmemoStoreBackend().trustOmemoIdentity(this, device, fingerprint); 350 } 351 352 /** 353 * Distrust the fingerprint/OmemoDevice tuple. 354 * The fingerprint must be the lowercase, hexadecimal fingerprint of the identityKey of the device and must 355 * be of length 64. 356 * @param device device 357 * @param fingerprint fingerprint 358 */ 359 public void distrustOmemoIdentity(OmemoDevice device, OmemoFingerprint fingerprint) { 360 getOmemoService().getOmemoStoreBackend().distrustOmemoIdentity(this, device, fingerprint); 361 } 362 363 /** 364 * Returns true, if the fingerprint/OmemoDevice tuple is trusted, otherwise false. 365 * The fingerprint must be the lowercase, hexadecimal fingerprint of the identityKey of the device and must 366 * be of length 64. 367 * @param device device 368 * @param fingerprint fingerprint 369 * @return 370 */ 371 public boolean isTrustedOmemoIdentity(OmemoDevice device, OmemoFingerprint fingerprint) { 372 return getOmemoService().getOmemoStoreBackend().isTrustedOmemoIdentity(this, device, fingerprint); 373 } 374 375 /** 376 * Returns true, if the fingerprint/OmemoDevice tuple is decided by the user. 377 * The fingerprint must be the lowercase, hexadecimal fingerprint of the identityKey of the device and must 378 * be of length 64. 379 * @param device device 380 * @param fingerprint fingerprint 381 * @return 382 */ 383 public boolean isDecidedOmemoIdentity(OmemoDevice device, OmemoFingerprint fingerprint) { 384 return getOmemoService().getOmemoStoreBackend().isDecidedOmemoIdentity(this, device, fingerprint); 385 } 386 387 /** 388 * Clear all other devices except this one from our device list and republish the list. 389 * 390 * @throws InterruptedException 391 * @throws SmackException 392 * @throws XMPPException.XMPPErrorException 393 * @throws CorruptedOmemoKeyException 394 */ 395 public void purgeDevices() throws SmackException, InterruptedException, XMPPException.XMPPErrorException, CorruptedOmemoKeyException { 396 getOmemoService().publishDeviceIdIfNeeded(this,true); 397 getOmemoService().publishBundle(this); 398 } 399 400 /** 401 * Generate fresh identity keys and bundle and publish it to the server. 402 * @throws SmackException 403 * @throws InterruptedException 404 * @throws XMPPException.XMPPErrorException 405 * @throws CorruptedOmemoKeyException 406 */ 407 public void regenerate() throws SmackException, InterruptedException, XMPPException.XMPPErrorException, CorruptedOmemoKeyException { 408 // create a new identity and publish new keys to the server 409 getOmemoService().regenerate(this, null); 410 getOmemoService().publishDeviceIdIfNeeded(this,false); 411 getOmemoService().publishBundle(this); 412 } 413 414 /** 415 * Send a ratchet update message. This can be used to advance the ratchet of a session in order to maintain forward 416 * secrecy. 417 * 418 * @param recipient recipient 419 * @throws UndecidedOmemoIdentityException When the trust of session with the recipient is not decided yet 420 * @throws CorruptedOmemoKeyException When the used identityKeys are corrupted 421 * @throws CryptoFailedException When something fails with the crypto 422 * @throws CannotEstablishOmemoSessionException When we can't establish a session with the recipient 423 */ 424 public void sendRatchetUpdateMessage(OmemoDevice recipient) 425 throws CorruptedOmemoKeyException, UndecidedOmemoIdentityException, CryptoFailedException, 426 CannotEstablishOmemoSessionException { 427 getOmemoService().sendOmemoRatchetUpdateMessage(this, recipient, false); 428 } 429 430 /** 431 * Create a new KeyTransportElement. This message will contain the AES-Key and IV that can be used eg. for encrypted 432 * Jingle file transfer. 433 * 434 * @param aesKey AES key to transport 435 * @param iv Initialization vector 436 * @param to list of recipient devices 437 * @return KeyTransportMessage 438 * @throws UndecidedOmemoIdentityException When the trust of session with the recipient is not decided yet 439 * @throws CorruptedOmemoKeyException When the used identityKeys are corrupted 440 * @throws CryptoFailedException When something fails with the crypto 441 * @throws CannotEstablishOmemoSessionException When we can't establish a session with the recipient 442 */ 443 public OmemoVAxolotlElement createKeyTransportElement(byte[] aesKey, byte[] iv, OmemoDevice ... to) 444 throws UndecidedOmemoIdentityException, CorruptedOmemoKeyException, CryptoFailedException, 445 CannotEstablishOmemoSessionException { 446 return getOmemoService().prepareOmemoKeyTransportElement(this, aesKey, iv, to); 447 } 448 449 /** 450 * Create a new Message from a encrypted OmemoMessageElement. 451 * Add ourselves as the sender and the encrypted element. 452 * Also tell the server to store the message despite a possible missing body. 453 * The body will be set to a hint message that we are using OMEMO. 454 * 455 * @param encrypted OmemoMessageElement 456 * @return Message containing the OMEMO element and some additional information 457 */ 458 Message finishMessage(OmemoVAxolotlElement encrypted) { 459 if (encrypted == null) { 460 return null; 461 } 462 463 Message chatMessage = new Message(); 464 chatMessage.setFrom(connection().getUser().asBareJid()); 465 chatMessage.addExtension(encrypted); 466 467 if (OmemoConfiguration.getAddOmemoHintBody()) { 468 chatMessage.setBody(BODY_OMEMO_HINT); 469 } 470 471 if (OmemoConfiguration.getAddMAMStorageProcessingHint()) { 472 StoreHint.set(chatMessage); 473 } 474 475 if (OmemoConfiguration.getAddEmeEncryptionHint()) { 476 chatMessage.addExtension(new ExplicitMessageEncryptionElement( 477 ExplicitMessageEncryptionElement.ExplicitMessageEncryptionProtocol.omemoVAxolotl)); 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 (NamedElement 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}