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; 045import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; 046import org.jivesoftware.smackx.ox.callback.backup.AskForBackupCodeCallback; 047import org.jivesoftware.smackx.ox.callback.backup.SecretKeyBackupSelectionCallback; 048import org.jivesoftware.smackx.ox.crypto.OpenPgpProvider; 049import org.jivesoftware.smackx.ox.element.CryptElement; 050import org.jivesoftware.smackx.ox.element.OpenPgpContentElement; 051import org.jivesoftware.smackx.ox.element.OpenPgpElement; 052import org.jivesoftware.smackx.ox.element.PubkeyElement; 053import org.jivesoftware.smackx.ox.element.PublicKeysListElement; 054import org.jivesoftware.smackx.ox.element.SecretkeyElement; 055import org.jivesoftware.smackx.ox.element.SignElement; 056import org.jivesoftware.smackx.ox.element.SigncryptElement; 057import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException; 058import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyException; 059import org.jivesoftware.smackx.ox.exception.MissingUserIdOnKeyException; 060import org.jivesoftware.smackx.ox.exception.NoBackupFoundException; 061import org.jivesoftware.smackx.ox.listener.CryptElementReceivedListener; 062import org.jivesoftware.smackx.ox.listener.SignElementReceivedListener; 063import org.jivesoftware.smackx.ox.listener.SigncryptElementReceivedListener; 064import org.jivesoftware.smackx.ox.store.definition.OpenPgpStore; 065import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore; 066import org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil; 067import org.jivesoftware.smackx.ox.util.SecretKeyBackupHelper; 068import org.jivesoftware.smackx.pep.PepEventListener; 069import org.jivesoftware.smackx.pep.PepListener; 070import org.jivesoftware.smackx.pep.PepManager; 071import org.jivesoftware.smackx.pubsub.LeafNode; 072import org.jivesoftware.smackx.pubsub.PubSubException; 073import org.jivesoftware.smackx.pubsub.PubSubFeature; 074 075import org.bouncycastle.openpgp.PGPException; 076import org.bouncycastle.openpgp.PGPPublicKeyRing; 077import org.bouncycastle.openpgp.PGPSecretKeyRing; 078import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; 079import org.jxmpp.jid.BareJid; 080import org.jxmpp.jid.EntityBareJid; 081import org.pgpainless.key.OpenPgpV4Fingerprint; 082import org.pgpainless.key.collection.PGPKeyRing; 083import org.pgpainless.key.protection.SecretKeyRingProtector; 084import org.pgpainless.util.BCUtil; 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 public void announceSupportAndPublish() 251 throws NoSuchAlgorithmException, NoSuchProviderException, InterruptedException, 252 PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, 253 SmackException.NotConnectedException, SmackException.NoResponseException, IOException, 254 InvalidAlgorithmParameterException, SmackException.NotLoggedInException, PGPException { 255 throwIfNoProviderSet(); 256 throwIfNotAuthenticated(); 257 258 OpenPgpV4Fingerprint primaryFingerprint = getOurFingerprint(); 259 260 if (primaryFingerprint == null) { 261 primaryFingerprint = generateAndImportKeyPair(getJidOrThrow()); 262 } 263 264 // Create <pubkey/> element 265 PubkeyElement pubkeyElement; 266 try { 267 pubkeyElement = createPubkeyElement(getJidOrThrow(), primaryFingerprint, new Date()); 268 } catch (MissingOpenPgpKeyException e) { 269 throw new AssertionError("Cannot publish our public key, since it is missing (MUST NOT happen!)"); 270 } 271 272 // publish it 273 publishPublicKey(pepManager, pubkeyElement, primaryFingerprint); 274 275 // Subscribe to public key changes 276 pepManager.addPepEventListener(PEP_NODE_PUBLIC_KEYS, PublicKeysListElement.class, pepPublicKeyListElementListener); 277 ServiceDiscoveryManager.getInstanceFor(connection()) 278 .addFeature(PEP_NODE_PUBLIC_KEYS_NOTIFY); 279 } 280 281 /** 282 * Generate a fresh OpenPGP key pair and import it. 283 * 284 * @param ourJid our {@link BareJid}. 285 * @return {@link OpenPgpV4Fingerprint} of the generated key. 286 * @throws NoSuchAlgorithmException if the JVM doesn't support one of the used algorithms. 287 * @throws InvalidAlgorithmParameterException if the used algorithm parameters are invalid. 288 * @throws NoSuchProviderException if we are missing a cryptographic provider. 289 * @throws PGPException PGP is brittle. 290 * @throws IOException IO is dangerous. 291 */ 292 public OpenPgpV4Fingerprint generateAndImportKeyPair(BareJid ourJid) 293 throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchProviderException, 294 PGPException, IOException { 295 296 throwIfNoProviderSet(); 297 OpenPgpStore store = provider.getStore(); 298 299 PGPKeyRing keys = generateKeyRing(ourJid); 300 importKeyRing(ourJid, keys); 301 302 OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(keys.getSecretKeys()); 303 304 store.setTrust(ourJid, fingerprint, OpenPgpTrustStore.Trust.trusted); 305 306 return fingerprint; 307 } 308 309 public PGPKeyRing generateKeyRing(BareJid ourJid) 310 throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException { 311 throwIfNoProviderSet(); 312 PGPKeyRing keys = provider.getStore().generateKeyRing(ourJid); 313 return keys; 314 } 315 316 private void importKeyRing(BareJid ourJid, PGPKeyRing keyRing) throws IOException, PGPException { 317 try { 318 provider.getStore().importSecretKey(ourJid, keyRing.getSecretKeys()); 319 provider.getStore().importPublicKey(ourJid, keyRing.getPublicKeys()); 320 } catch (MissingUserIdOnKeyException e) { 321 // This should never throw, since we set our jid literally one line above this comment. 322 throw new AssertionError(e); 323 } 324 } 325 326 /** 327 * Return the upper-case hex encoded OpenPGP v4 fingerprint of our key pair. 328 * 329 * @return fingerprint. 330 * @throws SmackException.NotLoggedInException in case we are not logged in. 331 * @throws IOException IO is dangerous. 332 * @throws PGPException PGP is brittle. 333 */ 334 public OpenPgpV4Fingerprint getOurFingerprint() 335 throws SmackException.NotLoggedInException, IOException, PGPException { 336 return getOpenPgpSelf().getSigningKeyFingerprint(); 337 } 338 339 /** 340 * Return an OpenPGP capable contact. 341 * This object can be used as an entry point to OpenPGP related API. 342 * 343 * @param jid {@link BareJid} of the contact. 344 * @return {@link OpenPgpContact}. 345 */ 346 public OpenPgpContact getOpenPgpContact(EntityBareJid jid) { 347 throwIfNoProviderSet(); 348 return provider.getStore().getOpenPgpContact(jid); 349 } 350 351 /** 352 * Return true, if we have a secret key available, otherwise false. 353 * 354 * @return true if secret key available 355 * 356 * @throws SmackException.NotLoggedInException If we are not logged in (we need to know our jid in order to look up 357 * our keys in the key store. 358 * @throws PGPException in case the keys in the store are damaged somehow. 359 * @throws IOException IO is dangerous. 360 */ 361 public boolean hasSecretKeysAvailable() throws SmackException.NotLoggedInException, PGPException, IOException { 362 throwIfNoProviderSet(); 363 return getOpenPgpSelf().hasSecretKeyAvailable(); 364 } 365 366 /** 367 * Determine, if we can sync secret keys using private PEP nodes as described in the XEP. 368 * Requirements on the server side are support for PEP and support for the whitelist access model of PubSub. 369 * 370 * @see <a href="https://xmpp.org/extensions/xep-0373.html#synchro-pep">XEP-0373 §5</a> 371 * 372 * @param connection XMPP connection 373 * @return true, if the server supports secret key backups, otherwise false. 374 * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error. 375 * @throws SmackException.NotConnectedException if we are not connected. 376 * @throws InterruptedException if the thread is interrupted. 377 * @throws SmackException.NoResponseException if the server doesn't respond. 378 */ 379 public static boolean serverSupportsSecretKeyBackups(XMPPConnection connection) 380 throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, 381 SmackException.NoResponseException { 382 return ServiceDiscoveryManager.getInstanceFor(connection) 383 .serverSupportsFeature(PubSubFeature.access_whitelist.toString()); 384 } 385 386 /** 387 * Remove the metadata listener. This method is mainly used in tests. 388 */ 389 public void stopMetadataListener() { 390 pepManager.removePepEventListener(pepPublicKeyListElementListener); 391 } 392 393 /** 394 * Upload the encrypted secret key to a private PEP node. 395 * 396 * @see <a href="https://xmpp.org/extensions/xep-0373.html#synchro-pep">XEP-0373 §5</a> 397 * 398 * @param selectKeyCallback callback, which will receive the users choice of which keys will be backed up. 399 * @return secret key passphrase used to encrypt the backup. 400 * 401 * @throws InterruptedException if the thread is interrupted. 402 * @throws PubSubException.NotALeafNodeException if the private node is not a {@link LeafNode}. 403 * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error. 404 * @throws SmackException.NotConnectedException if we are not connected. 405 * @throws SmackException.NoResponseException if the server doesn't respond. 406 * @throws SmackException.NotLoggedInException if we are not logged in. 407 * @throws IOException IO is dangerous. 408 * @throws SmackException.FeatureNotSupportedException if the server doesn't support the PubSub whitelist access model. 409 * @throws PGPException PGP is brittle 410 * @throws MissingOpenPgpKeyException in case we have no OpenPGP key pair to back up. 411 */ 412 public OpenPgpSecretKeyBackupPassphrase backupSecretKeyToServer(SecretKeyBackupSelectionCallback selectKeyCallback) 413 throws InterruptedException, PubSubException.NotALeafNodeException, 414 XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException, 415 SmackException.NotLoggedInException, IOException, 416 SmackException.FeatureNotSupportedException, PGPException, MissingOpenPgpKeyException { 417 OpenPgpSecretKeyBackupPassphrase passphrase = SecretKeyBackupHelper.generateBackupPassword(); 418 backupSecretKeyToServer(selectKeyCallback, passphrase); 419 return passphrase; 420 } 421 422 /** 423 * Upload the encrypted secret key to a private PEP node. 424 * The backup is encrypted using the provided secret key passphrase. 425 * 426 * @see <a href="https://xmpp.org/extensions/xep-0373.html#synchro-pep">XEP-0373 §5</a> 427 * 428 * @param selectKeyCallback callback, which will receive the users choice of which keys will be backed up. @param selectKeyCallback 429 * @param passphrase secret key passphrase 430 * 431 * @throws InterruptedException if the thread is interrupted. 432 * @throws PubSubException.NotALeafNodeException if the private node is not a {@link LeafNode}. 433 * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error. 434 * @throws SmackException.NotConnectedException if we are not connected. 435 * @throws SmackException.NoResponseException if the server doesn't respond. 436 * @throws SmackException.NotLoggedInException if we are not logged in. 437 * @throws IOException IO is dangerous. 438 * @throws SmackException.FeatureNotSupportedException if the server doesn't support the PubSub whitelist access model. 439 * @throws PGPException PGP is brittle 440 * @throws MissingOpenPgpKeyException in case we have no OpenPGP key pair to back up. 441 */ 442 public void backupSecretKeyToServer(SecretKeyBackupSelectionCallback selectKeyCallback, 443 OpenPgpSecretKeyBackupPassphrase passphrase) 444 throws InterruptedException, PubSubException.NotALeafNodeException, 445 XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException, 446 SmackException.NotLoggedInException, IOException, 447 SmackException.FeatureNotSupportedException, PGPException, MissingOpenPgpKeyException { 448 throwIfNoProviderSet(); 449 throwIfNotAuthenticated(); 450 451 BareJid ownJid = connection().getUser().asBareJid(); 452 453 PGPSecretKeyRingCollection secretKeyRings = provider.getStore().getSecretKeysOf(ownJid); 454 455 Set<OpenPgpV4Fingerprint> availableKeyPairs = new HashSet<>(); 456 for (PGPSecretKeyRing ring : secretKeyRings) { 457 availableKeyPairs.add(new OpenPgpV4Fingerprint(ring)); 458 } 459 460 Set<OpenPgpV4Fingerprint> selectedKeyPairs = selectKeyCallback.selectKeysToBackup(availableKeyPairs); 461 462 SecretkeyElement secretKey = SecretKeyBackupHelper.createSecretkeyElement(provider, ownJid, selectedKeyPairs, passphrase); 463 464 OpenPgpPubSubUtil.depositSecretKey(connection(), secretKey); 465 } 466 467 /** 468 * Delete the private {@link LeafNode} containing our secret key backup. 469 * 470 * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error. 471 * @throws SmackException.NotConnectedException if we are not connected. 472 * @throws InterruptedException if the thread gets interrupted. 473 * @throws SmackException.NoResponseException if the server doesn't respond. 474 * @throws SmackException.NotLoggedInException if we are not logged in. 475 */ 476 public void deleteSecretKeyServerBackup() 477 throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, 478 SmackException.NoResponseException, SmackException.NotLoggedInException { 479 throwIfNotAuthenticated(); 480 OpenPgpPubSubUtil.deleteSecretKeyNode(pepManager); 481 } 482 483 /** 484 * Fetch a secret key backup from the server and try to restore a selected secret key from it. 485 * 486 * @param codeCallback callback for prompting the user to provide the secret backup code. 487 * @return fingerprint of the restored secret key 488 * 489 * @throws InterruptedException if the thread gets interrupted. 490 * @throws PubSubException.NotALeafNodeException if the private node is not a {@link LeafNode}. 491 * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error. 492 * @throws SmackException.NotConnectedException if we are not connected. 493 * @throws SmackException.NoResponseException if the server doesn't respond. 494 * @throws InvalidBackupCodeException if the user-provided backup code is invalid. 495 * @throws SmackException.NotLoggedInException if we are not logged in 496 * @throws IOException IO is dangerous 497 * @throws MissingUserIdOnKeyException if the key that is to be imported is missing a user-id with our jid 498 * @throws NoBackupFoundException if no secret key backup has been found 499 * @throws PGPException in case the restored secret key is damaged. 500 */ 501 public OpenPgpV4Fingerprint restoreSecretKeyServerBackup(AskForBackupCodeCallback codeCallback) 502 throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, 503 SmackException.NotConnectedException, SmackException.NoResponseException, 504 InvalidBackupCodeException, SmackException.NotLoggedInException, IOException, MissingUserIdOnKeyException, 505 NoBackupFoundException, PGPException { 506 throwIfNoProviderSet(); 507 throwIfNotAuthenticated(); 508 SecretkeyElement backup = OpenPgpPubSubUtil.fetchSecretKey(pepManager); 509 if (backup == null) { 510 throw new NoBackupFoundException(); 511 } 512 513 OpenPgpSecretKeyBackupPassphrase backupCode = codeCallback.askForBackupCode(); 514 515 PGPSecretKeyRing secretKeys = SecretKeyBackupHelper.restoreSecretKeyBackup(backup, backupCode); 516 OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(secretKeys); 517 provider.getStore().importSecretKey(getJidOrThrow(), secretKeys); 518 provider.getStore().importPublicKey(getJidOrThrow(), BCUtil.publicKeyRingFromSecretKeyRing(secretKeys)); 519 520 getOpenPgpSelf().trust(fingerprint); 521 522 return new OpenPgpV4Fingerprint(secretKeys); 523 } 524 525 /* 526 Private stuff. 527 */ 528 529 private void processPublicKeysListElement(BareJid contact, PublicKeysListElement listElement) { 530 OpenPgpContact openPgpContact = getOpenPgpContact(contact.asEntityBareJidIfPossible()); 531 try { 532 openPgpContact.updateKeys(connection(), listElement); 533 } catch (Exception e) { 534 LOGGER.log(Level.WARNING, "Could not update contacts keys", e); 535 } 536 } 537 538 /** 539 * Decrypt and or verify an {@link OpenPgpElement} and return the decrypted {@link OpenPgpMessage}. 540 * 541 * @param element {@link OpenPgpElement} containing the message. 542 * @param sender {@link OpenPgpContact} who sent the message. 543 * 544 * @return decrypted and/or verified message 545 * 546 * @throws SmackException.NotLoggedInException in case we aren't logged in (we need to know our jid) 547 * @throws IOException IO error (reading keys, streams etc) 548 * @throws PGPException in case of an PGP error 549 */ 550 public OpenPgpMessage decryptOpenPgpElement(OpenPgpElement element, OpenPgpContact sender) 551 throws SmackException.NotLoggedInException, IOException, PGPException { 552 return provider.decryptAndOrVerify(getAuthenticatedConnectionOrThrow(), element, getOpenPgpSelf(), sender); 553 } 554 555 private void incomingChatMessageListener(final EntityBareJid from, final Message message, Chat chat) { 556 Async.go(new Runnable() { 557 @Override 558 public void run() { 559 OpenPgpElement element = message.getExtension(OpenPgpElement.class); 560 if (element == null) { 561 // Message does not contain an OpenPgpElement -> discard 562 return; 563 } 564 565 OpenPgpContact contact = getOpenPgpContact(from); 566 567 OpenPgpMessage decrypted = null; 568 OpenPgpContentElement contentElement = null; 569 try { 570 decrypted = decryptOpenPgpElement(element, contact); 571 contentElement = decrypted.getOpenPgpContentElement(); 572 } catch (PGPException e) { 573 LOGGER.log(Level.WARNING, "Could not decrypt incoming OpenPGP encrypted message", e); 574 } catch (XmlPullParserException | IOException e) { 575 LOGGER.log(Level.WARNING, "Invalid XML content of incoming OpenPGP encrypted message", e); 576 } catch (SmackException.NotLoggedInException e) { 577 LOGGER.log(Level.WARNING, "Cannot determine our JID, since we are not logged in.", e); 578 } 579 580 if (contentElement instanceof SigncryptElement) { 581 for (SigncryptElementReceivedListener l : signcryptElementReceivedListeners) { 582 l.signcryptElementReceived(contact, message, (SigncryptElement) contentElement, 583 decrypted.getMetadata()); 584 } 585 return; 586 } 587 588 if (contentElement instanceof SignElement) { 589 for (SignElementReceivedListener l : signElementReceivedListeners) { 590 l.signElementReceived(contact, message, (SignElement) contentElement, decrypted.getMetadata()); 591 } 592 return; 593 } 594 595 if (contentElement instanceof CryptElement) { 596 for (CryptElementReceivedListener l : cryptElementReceivedListeners) { 597 l.cryptElementReceived(contact, message, (CryptElement) contentElement, 598 decrypted.getMetadata()); 599 } 600 return; 601 } 602 603 else { 604 throw new AssertionError("Invalid element received: " + contentElement.getClass().getName()); 605 } 606 } 607 }); 608 } 609 610 /** 611 * Create a {@link PubkeyElement} which contains the OpenPGP public key of {@code owner} which belongs to 612 * the {@link OpenPgpV4Fingerprint} {@code fingerprint}. 613 * 614 * @param owner owner of the public key 615 * @param fingerprint fingerprint of the key 616 * @param date date of creation of the element 617 * @return {@link PubkeyElement} containing the key 618 * 619 * @throws MissingOpenPgpKeyException if the public key notated by the fingerprint cannot be found 620 */ 621 private PubkeyElement createPubkeyElement(BareJid owner, 622 OpenPgpV4Fingerprint fingerprint, 623 Date date) 624 throws MissingOpenPgpKeyException, IOException, PGPException { 625 PGPPublicKeyRing ring = provider.getStore().getPublicKeyRing(owner, fingerprint); 626 if (ring != null) { 627 byte[] keyBytes = ring.getEncoded(true); 628 return createPubkeyElement(keyBytes, date); 629 } 630 throw new MissingOpenPgpKeyException(owner, fingerprint); 631 } 632 633 /** 634 * Create a {@link PubkeyElement} which contains the given {@code data} base64 encoded. 635 * 636 * @param bytes byte representation of an OpenPGP public key 637 * @param date date of creation of the element 638 * @return {@link PubkeyElement} containing the key 639 */ 640 private static PubkeyElement createPubkeyElement(byte[] bytes, Date date) { 641 String base64EncodedOpenPgpPubKey = Base64.encodeToString(bytes); 642 return new PubkeyElement(new PubkeyElement.PubkeyDataElement(base64EncodedOpenPgpPubKey), date); 643 } 644 645 /** 646 * Register a {@link SigncryptElementReceivedListener} on the {@link OpenPgpManager}. 647 * That listener will get informed whenever a {@link SigncryptElement} has been received and successfully decrypted. 648 * 649 * Note: This method is not intended for clients to listen for incoming {@link SigncryptElement}s. 650 * Instead its purpose is to allow easy extension of XEP-0373 for custom OpenPGP profiles such as 651 * OpenPGP for XMPP: Instant Messaging. 652 * 653 * @param listener listener that gets registered 654 */ 655 public void registerSigncryptReceivedListener(SigncryptElementReceivedListener listener) { 656 signcryptElementReceivedListeners.add(listener); 657 } 658 659 /** 660 * Unregister a prior registered {@link SigncryptElementReceivedListener}. That listener will no longer get 661 * informed about incoming decrypted {@link SigncryptElement}s. 662 * 663 * @param listener listener that gets unregistered 664 */ 665 void unregisterSigncryptElementReceivedListener(SigncryptElementReceivedListener listener) { 666 signcryptElementReceivedListeners.remove(listener); 667 } 668 669 /** 670 * Register a {@link SignElementReceivedListener} on the {@link OpenPgpManager}. 671 * That listener will get informed whenever a {@link SignElement} has been received and successfully verified. 672 * 673 * Note: This method is not intended for clients to listen for incoming {@link SignElement}s. 674 * Instead its purpose is to allow easy extension of XEP-0373 for custom OpenPGP profiles such as 675 * OpenPGP for XMPP: Instant Messaging. 676 * 677 * @param listener listener that gets registered 678 */ 679 void registerSignElementReceivedListener(SignElementReceivedListener listener) { 680 signElementReceivedListeners.add(listener); 681 } 682 683 /** 684 * Unregister a prior registered {@link SignElementReceivedListener}. That listener will no longer get 685 * informed about incoming decrypted {@link SignElement}s. 686 * 687 * @param listener listener that gets unregistered 688 */ 689 void unregisterSignElementReceivedListener(SignElementReceivedListener listener) { 690 signElementReceivedListeners.remove(listener); 691 } 692 693 /** 694 * Register a {@link CryptElementReceivedListener} on the {@link OpenPgpManager}. 695 * That listener will get informed whenever a {@link CryptElement} has been received and successfully decrypted. 696 * 697 * Note: This method is not intended for clients to listen for incoming {@link CryptElement}s. 698 * Instead its purpose is to allow easy extension of XEP-0373 for custom OpenPGP profiles such as 699 * OpenPGP for XMPP: Instant Messaging. 700 * 701 * @param listener listener that gets registered 702 */ 703 void registerCryptElementReceivedListener(CryptElementReceivedListener listener) { 704 cryptElementReceivedListeners.add(listener); 705 } 706 707 /** 708 * Unregister a prior registered {@link CryptElementReceivedListener}. That listener will no longer get 709 * informed about incoming decrypted {@link CryptElement}s. 710 * 711 * @param listener listener that gets unregistered 712 */ 713 void unregisterCryptElementReceivedListener(CryptElementReceivedListener listener) { 714 cryptElementReceivedListeners.remove(listener); 715 } 716 717 /** 718 * Throw an {@link IllegalStateException} if no {@link OpenPgpProvider} is set. 719 * The OpenPgpProvider is used to process information related to RFC-4880. 720 */ 721 private void throwIfNoProviderSet() { 722 if (provider == null) { 723 throw new IllegalStateException("No OpenPgpProvider set!"); 724 } 725 } 726 727 /** 728 * Throw a {@link org.jivesoftware.smack.SmackException.NotLoggedInException} if the {@link XMPPConnection} of this 729 * manager is not authenticated at this point. 730 * 731 * @throws SmackException.NotLoggedInException if we are not authenticated 732 */ 733 private void throwIfNotAuthenticated() throws SmackException.NotLoggedInException { 734 if (!connection().isAuthenticated()) { 735 throw new SmackException.NotLoggedInException(); 736 } 737 } 738}