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