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