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