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}