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