001/** 002 * 003 * Copyright 2017 Florian Schmaus, 2018 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.ox; 018 019import static org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil.PEP_NODE_PUBLIC_KEYS; 020import static org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil.PEP_NODE_PUBLIC_KEYS_NOTIFY; 021import static org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil.publishPublicKey; 022 023import java.io.ByteArrayOutputStream; 024import java.io.IOException; 025import java.security.InvalidAlgorithmParameterException; 026import java.security.NoSuchAlgorithmException; 027import java.security.NoSuchProviderException; 028import java.util.Date; 029import java.util.HashSet; 030import java.util.Map; 031import java.util.Set; 032import java.util.WeakHashMap; 033import java.util.logging.Level; 034import java.util.logging.Logger; 035 036import org.jivesoftware.smack.Manager; 037import org.jivesoftware.smack.SmackException; 038import org.jivesoftware.smack.XMPPConnection; 039import org.jivesoftware.smack.XMPPException; 040import org.jivesoftware.smack.chat2.Chat; 041import org.jivesoftware.smack.chat2.ChatManager; 042import org.jivesoftware.smack.chat2.IncomingChatMessageListener; 043import org.jivesoftware.smack.packet.Message; 044import org.jivesoftware.smack.util.Async; 045import org.jivesoftware.smack.util.stringencoder.Base64; 046import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; 047import org.jivesoftware.smackx.ox.callback.backup.AskForBackupCodeCallback; 048import org.jivesoftware.smackx.ox.callback.backup.DisplayBackupCodeCallback; 049import org.jivesoftware.smackx.ox.callback.backup.SecretKeyBackupSelectionCallback; 050import org.jivesoftware.smackx.ox.crypto.OpenPgpProvider; 051import org.jivesoftware.smackx.ox.element.CryptElement; 052import org.jivesoftware.smackx.ox.element.OpenPgpContentElement; 053import org.jivesoftware.smackx.ox.element.OpenPgpElement; 054import org.jivesoftware.smackx.ox.element.PubkeyElement; 055import org.jivesoftware.smackx.ox.element.PublicKeysListElement; 056import org.jivesoftware.smackx.ox.element.SecretkeyElement; 057import org.jivesoftware.smackx.ox.element.SignElement; 058import org.jivesoftware.smackx.ox.element.SigncryptElement; 059import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException; 060import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyException; 061import org.jivesoftware.smackx.ox.exception.MissingUserIdOnKeyException; 062import org.jivesoftware.smackx.ox.exception.NoBackupFoundException; 063import org.jivesoftware.smackx.ox.listener.CryptElementReceivedListener; 064import org.jivesoftware.smackx.ox.listener.SignElementReceivedListener; 065import org.jivesoftware.smackx.ox.listener.SigncryptElementReceivedListener; 066import org.jivesoftware.smackx.ox.store.definition.OpenPgpStore; 067import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore; 068import org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil; 069import org.jivesoftware.smackx.ox.util.SecretKeyBackupHelper; 070import org.jivesoftware.smackx.pep.PEPListener; 071import org.jivesoftware.smackx.pep.PEPManager; 072import org.jivesoftware.smackx.pubsub.EventElement; 073import org.jivesoftware.smackx.pubsub.ItemsExtension; 074import org.jivesoftware.smackx.pubsub.LeafNode; 075import org.jivesoftware.smackx.pubsub.PayloadItem; 076import org.jivesoftware.smackx.pubsub.PubSubException; 077import org.jivesoftware.smackx.pubsub.PubSubFeature; 078 079import org.bouncycastle.openpgp.PGPException; 080import org.bouncycastle.openpgp.PGPPublicKey; 081import org.bouncycastle.openpgp.PGPPublicKeyRing; 082import org.bouncycastle.openpgp.PGPSecretKey; 083import org.bouncycastle.openpgp.PGPSecretKeyRing; 084import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; 085import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; 086import org.jxmpp.jid.BareJid; 087import org.jxmpp.jid.EntityBareJid; 088import org.pgpainless.key.OpenPgpV4Fingerprint; 089import org.pgpainless.key.collection.PGPKeyRing; 090import org.pgpainless.key.protection.SecretKeyRingProtector; 091import org.pgpainless.util.BCUtil; 092import org.xmlpull.v1.XmlPullParserException; 093 094/** 095 * Entry point for Smacks API for OpenPGP for XMPP. 096 * 097 * <h2>Setup</h2> 098 * 099 * In order to use OpenPGP for XMPP in Smack, just follow the following procedure.<br> 100 * <br> 101 * First, acquire an instance of the {@link OpenPgpManager} for your {@link XMPPConnection} using 102 * {@link #getInstanceFor(XMPPConnection)}. 103 * 104 * <pre> 105 * {@code 106 * OpenPgpManager openPgpManager = OpenPgpManager.getInstanceFor(connection); 107 * } 108 * </pre> 109 * 110 * You also need an {@link OpenPgpProvider}, as well as an {@link OpenPgpStore}. 111 * The provider must be registered using {@link #setOpenPgpProvider(OpenPgpProvider)}. 112 * 113 * <pre> 114 * {@code 115 * OpenPgpStore store = new FileBasedOpenPgpStore(storePath); 116 * OpenPgpProvider provider = new PainlessOpenPgpProvider(connection, store); 117 * openPgpManager.setOpenPgpProvider(provider); 118 * } 119 * </pre> 120 * 121 * It is also advised to register a custom {@link SecretKeyRingProtector} using 122 * {@link OpenPgpStore#setKeyRingProtector(SecretKeyRingProtector)} in order to be able to handle password protected 123 * secret keys.<br> 124 * <br> 125 * Speaking of keys, you can now check, if you have any keys available in your {@link OpenPgpStore} by doing 126 * {@link #hasSecretKeysAvailable()}.<br> 127 * <br> 128 * If you do, you can now announce support for OX and publish those keys using {@link #announceSupportAndPublish()}.<br> 129 * <br> 130 * Otherwise, you can either generate fresh keys using {@link #generateAndImportKeyPair(BareJid)}, 131 * or try to restore a secret key backup from your private PubSub node by doing 132 * {@link #restoreSecretKeyServerBackup(AskForBackupCodeCallback)}.<br> 133 * <br> 134 * In any case you should still do an {@link #announceSupportAndPublish()} afterwards. 135 * <br> 136 * <br> 137 * Contacts are represented by {@link OpenPgpContact}s in the context of OpenPGP for XMPP. You can get those by using 138 * {@link #getOpenPgpContact(EntityBareJid)}. The main function of {@link OpenPgpContact}s is to bundle information 139 * about the OpenPGP capabilities of a contact in one spot. The pendant to the {@link OpenPgpContact} is the 140 * {@link OpenPgpSelf}, which encapsulates your own OpenPGP identity. Both classes can be used to acquire information 141 * about the OpenPGP keys of a user. 142 * 143 * @see <a href="https://xmpp.org/extensions/xep-0373.html"> 144 * XEP-0373: OpenPGP for XMPP</a> 145 */ 146public final class OpenPgpManager extends Manager { 147 148 private static final Logger LOGGER = Logger.getLogger(OpenPgpManager.class.getName()); 149 150 /** 151 * Map of instances. 152 */ 153 private static final Map<XMPPConnection, OpenPgpManager> INSTANCES = new WeakHashMap<>(); 154 155 /** 156 * {@link OpenPgpProvider} responsible for processing keys, encrypting and decrypting messages and so on. 157 */ 158 private OpenPgpProvider provider; 159 160 private final Set<SigncryptElementReceivedListener> signcryptElementReceivedListeners = new HashSet<>(); 161 private final Set<SignElementReceivedListener> signElementReceivedListeners = new HashSet<>(); 162 private final Set<CryptElementReceivedListener> cryptElementReceivedListeners = new HashSet<>(); 163 164 /** 165 * Private constructor to avoid instantiation without putting the object into {@code INSTANCES}. 166 * 167 * @param connection xmpp connection. 168 */ 169 private OpenPgpManager(XMPPConnection connection) { 170 super(connection); 171 ChatManager.getInstanceFor(connection).addIncomingListener(incomingOpenPgpMessageListener); 172 } 173 174 /** 175 * Get the instance of the {@link OpenPgpManager} which belongs to the {@code connection}. 176 * 177 * @param connection xmpp connection. 178 * @return instance of the manager. 179 */ 180 public static OpenPgpManager getInstanceFor(XMPPConnection connection) { 181 OpenPgpManager manager = INSTANCES.get(connection); 182 if (manager == null) { 183 manager = new OpenPgpManager(connection); 184 INSTANCES.put(connection, manager); 185 } 186 return manager; 187 } 188 189 /** 190 * Return our own {@link BareJid}. 191 * 192 * @return our bareJid 193 * 194 * @throws SmackException.NotLoggedInException in case our connection is not logged in, which means our BareJid is unknown. 195 */ 196 public BareJid getJidOrThrow() throws SmackException.NotLoggedInException { 197 throwIfNotAuthenticated(); 198 return connection().getUser().asEntityBareJidOrThrow(); 199 } 200 201 /** 202 * Set the {@link OpenPgpProvider} which will be used to process incoming OpenPGP elements, 203 * as well as to execute cryptographic operations. 204 * 205 * @param provider OpenPgpProvider. 206 */ 207 public void setOpenPgpProvider(OpenPgpProvider provider) { 208 this.provider = provider; 209 } 210 211 public OpenPgpProvider getOpenPgpProvider() { 212 return provider; 213 } 214 215 /** 216 * Get our OpenPGP self. 217 * 218 * @return self 219 * @throws SmackException.NotLoggedInException if we are not logged in 220 */ 221 public OpenPgpSelf getOpenPgpSelf() throws SmackException.NotLoggedInException { 222 throwIfNoProviderSet(); 223 return new OpenPgpSelf(getJidOrThrow(), provider.getStore()); 224 } 225 226 /** 227 * Generate a fresh OpenPGP key pair, given we don't have one already. 228 * Publish the public key to the Public Key Node and update the Public Key Metadata Node with our keys fingerprint. 229 * Lastly register a {@link PEPListener} which listens for updates to Public Key Metadata Nodes. 230 * 231 * @throws NoSuchAlgorithmException if we are missing an algorithm to generate a fresh key pair. 232 * @throws NoSuchProviderException if we are missing a suitable {@link java.security.Provider}. 233 * @throws InterruptedException if the thread gets interrupted. 234 * @throws PubSubException.NotALeafNodeException if one of the PubSub nodes is not a {@link LeafNode}. 235 * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error. 236 * @throws SmackException.NotConnectedException if we are not connected. 237 * @throws SmackException.NoResponseException if the server doesn't respond. 238 * @throws IOException IO is dangerous. 239 * @throws InvalidAlgorithmParameterException if illegal algorithm parameters are used for key generation. 240 * @throws SmackException.NotLoggedInException if we are not logged in. 241 * @throws PGPException if something goes wrong during key loading/generating 242 */ 243 public void announceSupportAndPublish() 244 throws NoSuchAlgorithmException, NoSuchProviderException, InterruptedException, 245 PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, 246 SmackException.NotConnectedException, SmackException.NoResponseException, IOException, 247 InvalidAlgorithmParameterException, SmackException.NotLoggedInException, PGPException { 248 throwIfNoProviderSet(); 249 throwIfNotAuthenticated(); 250 251 OpenPgpV4Fingerprint primaryFingerprint = getOurFingerprint(); 252 253 if (primaryFingerprint == null) { 254 primaryFingerprint = generateAndImportKeyPair(getJidOrThrow()); 255 } 256 257 // Create <pubkey/> element 258 PubkeyElement pubkeyElement; 259 try { 260 pubkeyElement = createPubkeyElement(getJidOrThrow(), primaryFingerprint, new Date()); 261 } catch (MissingOpenPgpKeyException e) { 262 throw new AssertionError("Cannot publish our public key, since it is missing (MUST NOT happen!)"); 263 } 264 265 // publish it 266 publishPublicKey(connection(), pubkeyElement, primaryFingerprint); 267 268 // Subscribe to public key changes 269 PEPManager.getInstanceFor(connection()).addPEPListener(metadataListener); 270 ServiceDiscoveryManager.getInstanceFor(connection()) 271 .addFeature(PEP_NODE_PUBLIC_KEYS_NOTIFY); 272 } 273 274 /** 275 * Generate a fresh OpenPGP key pair and import it. 276 * 277 * @param ourJid our {@link BareJid}. 278 * @return {@link OpenPgpV4Fingerprint} of the generated key. 279 * @throws NoSuchAlgorithmException if the JVM doesn't support one of the used algorithms. 280 * @throws InvalidAlgorithmParameterException if the used algorithm parameters are invalid. 281 * @throws NoSuchProviderException if we are missing a cryptographic provider. 282 * @throws PGPException PGP is brittle. 283 * @throws IOException IO is dangerous. 284 */ 285 public OpenPgpV4Fingerprint generateAndImportKeyPair(BareJid ourJid) 286 throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchProviderException, 287 PGPException, IOException { 288 289 throwIfNoProviderSet(); 290 OpenPgpStore store = provider.getStore(); 291 PGPKeyRing keys = store.generateKeyRing(ourJid); 292 try { 293 store.importSecretKey(ourJid, keys.getSecretKeys()); 294 store.importPublicKey(ourJid, keys.getPublicKeys()); 295 } catch (MissingUserIdOnKeyException e) { 296 // This should never throw, since we set our jid literally one line above this comment. 297 throw new AssertionError(e); 298 } 299 300 OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(keys.getSecretKeys()); 301 302 store.setTrust(ourJid, fingerprint, OpenPgpTrustStore.Trust.trusted); 303 304 return fingerprint; 305 } 306 307 /** 308 * Return the upper-case hex encoded OpenPGP v4 fingerprint of our key pair. 309 * 310 * @return fingerprint. 311 * @throws SmackException.NotLoggedInException in case we are not logged in. 312 * @throws IOException IO is dangerous. 313 * @throws PGPException PGP is brittle. 314 */ 315 public OpenPgpV4Fingerprint getOurFingerprint() 316 throws SmackException.NotLoggedInException, IOException, PGPException { 317 return getOpenPgpSelf().getSigningKeyFingerprint(); 318 } 319 320 /** 321 * Return an OpenPGP capable contact. 322 * This object can be used as an entry point to OpenPGP related API. 323 * 324 * @param jid {@link BareJid} of the contact. 325 * @return {@link OpenPgpContact}. 326 */ 327 public OpenPgpContact getOpenPgpContact(EntityBareJid jid) { 328 throwIfNoProviderSet(); 329 return provider.getStore().getOpenPgpContact(jid); 330 } 331 332 /** 333 * Return true, if we have a secret key available, otherwise false. 334 * 335 * @return true if secret key available 336 * 337 * @throws SmackException.NotLoggedInException If we are not logged in (we need to know our jid in order to look up 338 * our keys in the key store. 339 * @throws PGPException in case the keys in the store are damaged somehow. 340 * @throws IOException IO is dangerous. 341 */ 342 public boolean hasSecretKeysAvailable() throws SmackException.NotLoggedInException, PGPException, IOException { 343 throwIfNoProviderSet(); 344 return getOpenPgpSelf().hasSecretKeyAvailable(); 345 } 346 347 /** 348 * Determine, if we can sync secret keys using private PEP nodes as described in the XEP. 349 * Requirements on the server side are support for PEP and support for the whitelist access model of PubSub. 350 * 351 * @see <a href="https://xmpp.org/extensions/xep-0373.html#synchro-pep">XEP-0373 §5</a> 352 * 353 * @param connection XMPP connection 354 * @return true, if the server supports secret key backups, otherwise false. 355 * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error. 356 * @throws SmackException.NotConnectedException if we are not connected. 357 * @throws InterruptedException if the thread is interrupted. 358 * @throws SmackException.NoResponseException if the server doesn't respond. 359 */ 360 public static boolean serverSupportsSecretKeyBackups(XMPPConnection connection) 361 throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, 362 SmackException.NoResponseException { 363 return ServiceDiscoveryManager.getInstanceFor(connection) 364 .serverSupportsFeature(PubSubFeature.access_whitelist.toString()); 365 } 366 367 /** 368 * Remove the metadata listener. This method is mainly used in tests. 369 */ 370 public void stopMetadataListener() { 371 PEPManager.getInstanceFor(connection()).removePEPListener(metadataListener); 372 } 373 374 /** 375 * Upload the encrypted secret key to a private PEP node. 376 * 377 * @see <a href="https://xmpp.org/extensions/xep-0373.html#synchro-pep">XEP-0373 §5</a> 378 * 379 * @param displayCodeCallback callback, which will receive the backup password used to encrypt the secret key. 380 * @param selectKeyCallback callback, which will receive the users choice of which keys will be backed up. 381 * @throws InterruptedException if the thread is interrupted. 382 * @throws PubSubException.NotALeafNodeException if the private node is not a {@link LeafNode}. 383 * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error. 384 * @throws SmackException.NotConnectedException if we are not connected. 385 * @throws SmackException.NoResponseException if the server doesn't respond. 386 * @throws SmackException.NotLoggedInException if we are not logged in. 387 * @throws IOException IO is dangerous. 388 * @throws SmackException.FeatureNotSupportedException if the server doesn't support the PubSub whitelist access model. 389 * @throws PGPException PGP is brittle 390 * @throws MissingOpenPgpKeyException in case we have no OpenPGP key pair to back up. 391 */ 392 public void backupSecretKeyToServer(DisplayBackupCodeCallback displayCodeCallback, 393 SecretKeyBackupSelectionCallback selectKeyCallback) 394 throws InterruptedException, PubSubException.NotALeafNodeException, 395 XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException, 396 SmackException.NotLoggedInException, IOException, 397 SmackException.FeatureNotSupportedException, PGPException, MissingOpenPgpKeyException { 398 throwIfNoProviderSet(); 399 throwIfNotAuthenticated(); 400 401 BareJid ownJid = connection().getUser().asBareJid(); 402 403 String backupCode = SecretKeyBackupHelper.generateBackupPassword(); 404 405 PGPSecretKeyRingCollection secretKeyRings = provider.getStore().getSecretKeysOf(ownJid); 406 407 Set<OpenPgpV4Fingerprint> availableKeyPairs = new HashSet<>(); 408 for (PGPSecretKeyRing ring : secretKeyRings) { 409 availableKeyPairs.add(new OpenPgpV4Fingerprint(ring)); 410 } 411 412 Set<OpenPgpV4Fingerprint> selectedKeyPairs = selectKeyCallback.selectKeysToBackup(availableKeyPairs); 413 414 SecretkeyElement secretKey = SecretKeyBackupHelper.createSecretkeyElement(provider, ownJid, selectedKeyPairs, backupCode); 415 416 OpenPgpPubSubUtil.depositSecretKey(connection(), secretKey); 417 displayCodeCallback.displayBackupCode(backupCode); 418 } 419 420 /** 421 * Delete the private {@link LeafNode} containing our secret key backup. 422 * 423 * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error. 424 * @throws SmackException.NotConnectedException if we are not connected. 425 * @throws InterruptedException if the thread gets interrupted. 426 * @throws SmackException.NoResponseException if the server doesn't respond. 427 * @throws SmackException.NotLoggedInException if we are not logged in. 428 */ 429 public void deleteSecretKeyServerBackup() 430 throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, 431 SmackException.NoResponseException, SmackException.NotLoggedInException { 432 throwIfNotAuthenticated(); 433 OpenPgpPubSubUtil.deleteSecretKeyNode(connection()); 434 } 435 436 /** 437 * Fetch a secret key backup from the server and try to restore a selected secret key from it. 438 * 439 * @param codeCallback callback for prompting the user to provide the secret backup code. 440 * @return fingerprint of the restored secret key 441 * 442 * @throws InterruptedException if the thread gets interrupted. 443 * @throws PubSubException.NotALeafNodeException if the private node is not a {@link LeafNode}. 444 * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error. 445 * @throws SmackException.NotConnectedException if we are not connected. 446 * @throws SmackException.NoResponseException if the server doesn't respond. 447 * @throws InvalidBackupCodeException if the user-provided backup code is invalid. 448 * @throws SmackException.NotLoggedInException if we are not logged in 449 * @throws IOException IO is dangerous 450 * @throws MissingUserIdOnKeyException if the key that is to be imported is missing a user-id with our jid 451 * @throws NoBackupFoundException if no secret key backup has been found 452 * @throws PGPException in case the restored secret key is damaged. 453 */ 454 public OpenPgpV4Fingerprint restoreSecretKeyServerBackup(AskForBackupCodeCallback codeCallback) 455 throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, 456 SmackException.NotConnectedException, SmackException.NoResponseException, 457 InvalidBackupCodeException, SmackException.NotLoggedInException, IOException, MissingUserIdOnKeyException, 458 NoBackupFoundException, PGPException { 459 throwIfNoProviderSet(); 460 throwIfNotAuthenticated(); 461 SecretkeyElement backup = OpenPgpPubSubUtil.fetchSecretKey(connection()); 462 if (backup == null) { 463 throw new NoBackupFoundException(); 464 } 465 466 String backupCode = codeCallback.askForBackupCode(); 467 468 PGPSecretKeyRing secretKeys = SecretKeyBackupHelper.restoreSecretKeyBackup(backup, backupCode); 469 provider.getStore().importSecretKey(getJidOrThrow(), secretKeys); 470 provider.getStore().importPublicKey(getJidOrThrow(), BCUtil.publicKeyRingFromSecretKeyRing(secretKeys)); 471 472 ByteArrayOutputStream buffer = new ByteArrayOutputStream(2048); 473 for (PGPSecretKey sk : secretKeys) { 474 PGPPublicKey pk = sk.getPublicKey(); 475 if (pk != null) pk.encode(buffer); 476 } 477 PGPPublicKeyRing publicKeys = new PGPPublicKeyRing(buffer.toByteArray(), new BcKeyFingerprintCalculator()); 478 provider.getStore().importPublicKey(getJidOrThrow(), publicKeys); 479 480 return new OpenPgpV4Fingerprint(secretKeys); 481 } 482 483 /* 484 Private stuff. 485 */ 486 487 /** 488 * {@link PEPListener} that listens for changes to the OX public keys metadata node. 489 * 490 * @see <a href="https://xmpp.org/extensions/xep-0373.html#pubsub-notifications">XEP-0373 §4.4</a> 491 */ 492 private final PEPListener metadataListener = new PEPListener() { 493 @Override 494 public void eventReceived(final EntityBareJid from, final EventElement event, final Message message) { 495 if (PEP_NODE_PUBLIC_KEYS.equals(event.getEvent().getNode())) { 496 final BareJid contact = from.asBareJid(); 497 LOGGER.log(Level.INFO, "Received OpenPGP metadata update from " + contact); 498 Async.go(new Runnable() { 499 @Override 500 public void run() { 501 ItemsExtension items = (ItemsExtension) event.getExtensions().get(0); 502 PayloadItem<?> payload = (PayloadItem) items.getItems().get(0); 503 PublicKeysListElement listElement = (PublicKeysListElement) payload.getPayload(); 504 505 processPublicKeysListElement(from, listElement); 506 } 507 }, "ProcessOXMetadata"); 508 } 509 } 510 }; 511 512 private void processPublicKeysListElement(BareJid contact, PublicKeysListElement listElement) { 513 OpenPgpContact openPgpContact = getOpenPgpContact(contact.asEntityBareJidIfPossible()); 514 try { 515 openPgpContact.updateKeys(connection(), listElement); 516 } catch (Exception e) { 517 LOGGER.log(Level.WARNING, "Could not update contacts keys", e); 518 } 519 } 520 521 public OpenPgpMessage decryptOpenPgpElement(OpenPgpElement element, OpenPgpContact contact) throws SmackException.NotLoggedInException, IOException, PGPException { 522 return provider.decryptAndOrVerify(element, getOpenPgpSelf(), contact); 523 } 524 525 private final IncomingChatMessageListener incomingOpenPgpMessageListener = 526 new IncomingChatMessageListener() { 527 @Override 528 public void newIncomingMessage(final EntityBareJid from, final Message message, Chat chat) { 529 Async.go(new Runnable() { 530 @Override 531 public void run() { 532 OpenPgpElement element = message.getExtension(OpenPgpElement.ELEMENT, OpenPgpElement.NAMESPACE); 533 if (element == null) { 534 // Message does not contain an OpenPgpElement -> discard 535 return; 536 } 537 538 OpenPgpContact contact = getOpenPgpContact(from); 539 540 OpenPgpMessage decrypted = null; 541 OpenPgpContentElement contentElement = null; 542 try { 543 decrypted = decryptOpenPgpElement(element, contact); 544 contentElement = decrypted.getOpenPgpContentElement(); 545 } catch (PGPException e) { 546 LOGGER.log(Level.WARNING, "Could not decrypt incoming OpenPGP encrypted message", e); 547 } catch (XmlPullParserException | IOException e) { 548 LOGGER.log(Level.WARNING, "Invalid XML content of incoming OpenPGP encrypted message", e); 549 } catch (SmackException.NotLoggedInException e) { 550 LOGGER.log(Level.WARNING, "Cannot determine our JID, since we are not logged in.", e); 551 } 552 553 if (contentElement instanceof SigncryptElement) { 554 for (SigncryptElementReceivedListener l : signcryptElementReceivedListeners) { 555 l.signcryptElementReceived(contact, message, (SigncryptElement) contentElement, decrypted.getMetadata()); 556 } 557 return; 558 } 559 560 if (contentElement instanceof SignElement) { 561 for (SignElementReceivedListener l : signElementReceivedListeners) { 562 l.signElementReceived(contact, message, (SignElement) contentElement, decrypted.getMetadata()); 563 } 564 return; 565 } 566 567 if (contentElement instanceof CryptElement) { 568 for (CryptElementReceivedListener l : cryptElementReceivedListeners) { 569 l.cryptElementReceived(contact, message, (CryptElement) contentElement, decrypted.getMetadata()); 570 } 571 return; 572 } 573 574 else { 575 throw new AssertionError("Invalid element received: " + contentElement.getClass().getName()); 576 } 577 } 578 }); 579 } 580 }; 581 582 /** 583 * Create a {@link PubkeyElement} which contains the OpenPGP public key of {@code owner} which belongs to 584 * the {@link OpenPgpV4Fingerprint} {@code fingerprint}. 585 * 586 * @param owner owner of the public key 587 * @param fingerprint fingerprint of the key 588 * @param date date of creation of the element 589 * @return {@link PubkeyElement} containing the key 590 * 591 * @throws MissingOpenPgpKeyException if the public key notated by the fingerprint cannot be found 592 */ 593 private PubkeyElement createPubkeyElement(BareJid owner, 594 OpenPgpV4Fingerprint fingerprint, 595 Date date) 596 throws MissingOpenPgpKeyException, IOException, PGPException { 597 PGPPublicKeyRing ring = provider.getStore().getPublicKeyRing(owner, fingerprint); 598 if (ring != null) { 599 byte[] keyBytes = ring.getEncoded(true); 600 return createPubkeyElement(keyBytes, date); 601 } 602 throw new MissingOpenPgpKeyException(owner, fingerprint); 603 } 604 605 /** 606 * Create a {@link PubkeyElement} which contains the given {@code data} base64 encoded. 607 * 608 * @param bytes byte representation of an OpenPGP public key 609 * @param date date of creation of the element 610 * @return {@link PubkeyElement} containing the key 611 */ 612 private static PubkeyElement createPubkeyElement(byte[] bytes, Date date) { 613 return new PubkeyElement(new PubkeyElement.PubkeyDataElement(Base64.encode(bytes)), date); 614 } 615 616 /** 617 * Register a {@link SigncryptElementReceivedListener} on the {@link OpenPgpManager}. 618 * That listener will get informed whenever a {@link SigncryptElement} has been received and successfully decrypted. 619 * 620 * Note: This method is not intended for clients to listen for incoming {@link SigncryptElement}s. 621 * Instead its purpose is to allow easy extension of XEP-0373 for custom OpenPGP profiles such as 622 * OpenPGP for XMPP: Instant Messaging. 623 * 624 * @param listener listener that gets registered 625 */ 626 public void registerSigncryptReceivedListener(SigncryptElementReceivedListener listener) { 627 signcryptElementReceivedListeners.add(listener); 628 } 629 630 /** 631 * Unregister a prior registered {@link SigncryptElementReceivedListener}. That listener will no longer get 632 * informed about incoming decrypted {@link SigncryptElement}s. 633 * 634 * @param listener listener that gets unregistered 635 */ 636 void unregisterSigncryptElementReceivedListener(SigncryptElementReceivedListener listener) { 637 signcryptElementReceivedListeners.remove(listener); 638 } 639 640 /** 641 * Register a {@link SignElementReceivedListener} on the {@link OpenPgpManager}. 642 * That listener will get informed whenever a {@link SignElement} has been received and successfully verified. 643 * 644 * Note: This method is not intended for clients to listen for incoming {@link SignElement}s. 645 * Instead its purpose is to allow easy extension of XEP-0373 for custom OpenPGP profiles such as 646 * OpenPGP for XMPP: Instant Messaging. 647 * 648 * @param listener listener that gets registered 649 */ 650 void registerSignElementReceivedListener(SignElementReceivedListener listener) { 651 signElementReceivedListeners.add(listener); 652 } 653 654 /** 655 * Unregister a prior registered {@link SignElementReceivedListener}. That listener will no longer get 656 * informed about incoming decrypted {@link SignElement}s. 657 * 658 * @param listener listener that gets unregistered 659 */ 660 void unregisterSignElementReceivedListener(SignElementReceivedListener listener) { 661 signElementReceivedListeners.remove(listener); 662 } 663 664 /** 665 * Register a {@link CryptElementReceivedListener} on the {@link OpenPgpManager}. 666 * That listener will get informed whenever a {@link CryptElement} has been received and successfully decrypted. 667 * 668 * Note: This method is not intended for clients to listen for incoming {@link CryptElement}s. 669 * Instead its purpose is to allow easy extension of XEP-0373 for custom OpenPGP profiles such as 670 * OpenPGP for XMPP: Instant Messaging. 671 * 672 * @param listener listener that gets registered 673 */ 674 void registerCryptElementReceivedListener(CryptElementReceivedListener listener) { 675 cryptElementReceivedListeners.add(listener); 676 } 677 678 /** 679 * Unregister a prior registered {@link CryptElementReceivedListener}. That listener will no longer get 680 * informed about incoming decrypted {@link CryptElement}s. 681 * 682 * @param listener listener that gets unregistered 683 */ 684 void unregisterCryptElementReceivedListener(CryptElementReceivedListener listener) { 685 cryptElementReceivedListeners.remove(listener); 686 } 687 688 /** 689 * Throw an {@link IllegalStateException} if no {@link OpenPgpProvider} is set. 690 * The OpenPgpProvider is used to process information related to RFC-4880. 691 */ 692 private void throwIfNoProviderSet() { 693 if (provider == null) { 694 throw new IllegalStateException("No OpenPgpProvider set!"); 695 } 696 } 697 698 /** 699 * Throw a {@link org.jivesoftware.smack.SmackException.NotLoggedInException} if the {@link XMPPConnection} of this 700 * manager is not authenticated at this point. 701 * 702 * @throws SmackException.NotLoggedInException if we are not authenticated 703 */ 704 private void throwIfNotAuthenticated() throws SmackException.NotLoggedInException { 705 if (!connection().isAuthenticated()) { 706 throw new SmackException.NotLoggedInException(); 707 } 708 } 709}