/** * * Copyright 2017-2020 Florian Schmaus, 2018 Paul Schaub. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.smackx.ox; import static org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil.PEP_NODE_PUBLIC_KEYS; import static org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil.PEP_NODE_PUBLIC_KEYS_NOTIFY; import static org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil.publishPublicKey; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.util.Date; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; import org.jivesoftware.smack.Manager; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.chat2.Chat; import org.jivesoftware.smack.chat2.ChatManager; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.util.Async; import org.jivesoftware.smack.util.stringencoder.Base64; import org.jivesoftware.smack.xml.XmlPullParserException; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.ox.callback.backup.AskForBackupCodeCallback; import org.jivesoftware.smackx.ox.callback.backup.DisplayBackupCodeCallback; import org.jivesoftware.smackx.ox.callback.backup.SecretKeyBackupSelectionCallback; import org.jivesoftware.smackx.ox.crypto.OpenPgpProvider; import org.jivesoftware.smackx.ox.element.CryptElement; import org.jivesoftware.smackx.ox.element.OpenPgpContentElement; import org.jivesoftware.smackx.ox.element.OpenPgpElement; import org.jivesoftware.smackx.ox.element.PubkeyElement; import org.jivesoftware.smackx.ox.element.PublicKeysListElement; import org.jivesoftware.smackx.ox.element.SecretkeyElement; import org.jivesoftware.smackx.ox.element.SignElement; import org.jivesoftware.smackx.ox.element.SigncryptElement; import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException; import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyException; import org.jivesoftware.smackx.ox.exception.MissingUserIdOnKeyException; import org.jivesoftware.smackx.ox.exception.NoBackupFoundException; import org.jivesoftware.smackx.ox.listener.CryptElementReceivedListener; import org.jivesoftware.smackx.ox.listener.SignElementReceivedListener; import org.jivesoftware.smackx.ox.listener.SigncryptElementReceivedListener; import org.jivesoftware.smackx.ox.store.definition.OpenPgpStore; import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore; import org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil; import org.jivesoftware.smackx.ox.util.SecretKeyBackupHelper; import org.jivesoftware.smackx.pep.PepEventListener; import org.jivesoftware.smackx.pep.PepListener; import org.jivesoftware.smackx.pep.PepManager; import org.jivesoftware.smackx.pubsub.LeafNode; import org.jivesoftware.smackx.pubsub.PubSubException; import org.jivesoftware.smackx.pubsub.PubSubFeature; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; import org.jxmpp.jid.BareJid; import org.jxmpp.jid.EntityBareJid; import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.collection.PGPKeyRing; import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.util.BCUtil; /** * Entry point for Smacks API for OpenPGP for XMPP. * *

Setup

* * In order to use OpenPGP for XMPP in Smack, just follow the following procedure.
*
* First, acquire an instance of the {@link OpenPgpManager} for your {@link XMPPConnection} using * {@link #getInstanceFor(XMPPConnection)}. * *
 * {@code
 * OpenPgpManager openPgpManager = OpenPgpManager.getInstanceFor(connection);
 * }
 * 
* * You also need an {@link OpenPgpProvider}, as well as an {@link OpenPgpStore}. * The provider must be registered using {@link #setOpenPgpProvider(OpenPgpProvider)}. * *
 * {@code
 * OpenPgpStore store = new FileBasedOpenPgpStore(storePath);
 * OpenPgpProvider provider = new PainlessOpenPgpProvider(connection, store);
 * openPgpManager.setOpenPgpProvider(provider);
 * }
 * 
* * It is also advised to register a custom {@link SecretKeyRingProtector} using * {@link OpenPgpStore#setKeyRingProtector(SecretKeyRingProtector)} in order to be able to handle password protected * secret keys.
*
* Speaking of keys, you can now check, if you have any keys available in your {@link OpenPgpStore} by doing * {@link #hasSecretKeysAvailable()}.
*
* If you do, you can now announce support for OX and publish those keys using {@link #announceSupportAndPublish()}.
*
* Otherwise, you can either generate fresh keys using {@link #generateAndImportKeyPair(BareJid)}, * or try to restore a secret key backup from your private PubSub node by doing * {@link #restoreSecretKeyServerBackup(AskForBackupCodeCallback)}.
*
* In any case you should still do an {@link #announceSupportAndPublish()} afterwards. *
*
* Contacts are represented by {@link OpenPgpContact}s in the context of OpenPGP for XMPP. You can get those by using * {@link #getOpenPgpContact(EntityBareJid)}. The main function of {@link OpenPgpContact}s is to bundle information * about the OpenPGP capabilities of a contact in one spot. The pendant to the {@link OpenPgpContact} is the * {@link OpenPgpSelf}, which encapsulates your own OpenPGP identity. Both classes can be used to acquire information * about the OpenPGP keys of a user. * *

Elements

* * OpenPGP for XMPP defines multiple different element classes which contain the users messages. * The outermost element is the {@link OpenPgpElement}, which contains an OpenPGP encrypted content element. * * The content can be either a {@link SignElement}, {@link CryptElement} or {@link SigncryptElement}, depending on the use-case. * Those content elements contain the actual payload. If an {@link OpenPgpElement} is decrypted, it will be returned in * form of an {@link OpenPgpMessage}, which represents the decrypted message + metadata. * * @see * XEP-0373: OpenPGP for XMPP */ public final class OpenPgpManager extends Manager { private static final Logger LOGGER = Logger.getLogger(OpenPgpManager.class.getName()); /** * Map of instances. */ private static final Map INSTANCES = new WeakHashMap<>(); /** * {@link OpenPgpProvider} responsible for processing keys, encrypting and decrypting messages and so on. */ private OpenPgpProvider provider; private final PepManager pepManager; private final Set signcryptElementReceivedListeners = new HashSet<>(); private final Set signElementReceivedListeners = new HashSet<>(); private final Set cryptElementReceivedListeners = new HashSet<>(); @SuppressWarnings("UnnecessaryLambda") private final PepEventListener pepPublicKeyListElementListener = (from, listElement, id, message) -> processPublicKeysListElement(from, listElement);; /** * Private constructor to avoid instantiation without putting the object into {@code INSTANCES}. * * @param connection xmpp connection. */ private OpenPgpManager(XMPPConnection connection) { super(connection); ChatManager.getInstanceFor(connection).addIncomingListener(this::incomingChatMessageListener); pepManager = PepManager.getInstanceFor(connection); } /** * Get the instance of the {@link OpenPgpManager} which belongs to the {@code connection}. * * @param connection xmpp connection. * @return instance of the manager. */ public static synchronized OpenPgpManager getInstanceFor(XMPPConnection connection) { OpenPgpManager manager = INSTANCES.get(connection); if (manager == null) { manager = new OpenPgpManager(connection); INSTANCES.put(connection, manager); } return manager; } /** * Return our own {@link BareJid}. * * @return our bareJid * * @throws SmackException.NotLoggedInException in case our connection is not logged in, which means our BareJid is unknown. */ public BareJid getJidOrThrow() throws SmackException.NotLoggedInException { throwIfNotAuthenticated(); return connection().getUser().asEntityBareJidOrThrow(); } /** * Set the {@link OpenPgpProvider} which will be used to process incoming OpenPGP elements, * as well as to execute cryptographic operations. * * @param provider OpenPgpProvider. */ public void setOpenPgpProvider(OpenPgpProvider provider) { this.provider = provider; } public OpenPgpProvider getOpenPgpProvider() { return provider; } /** * Get our OpenPGP self. * * @return self TODO javadoc me please * @throws SmackException.NotLoggedInException if we are not logged in */ public OpenPgpSelf getOpenPgpSelf() throws SmackException.NotLoggedInException { throwIfNoProviderSet(); return new OpenPgpSelf(getJidOrThrow(), provider.getStore()); } /** * Generate a fresh OpenPGP key pair, given we don't have one already. * Publish the public key to the Public Key Node and update the Public Key Metadata Node with our keys fingerprint. * Lastly register a {@link PepListener} which listens for updates to Public Key Metadata Nodes. * * @throws NoSuchAlgorithmException if we are missing an algorithm to generate a fresh key pair. * @throws NoSuchProviderException if we are missing a suitable {@link java.security.Provider}. * @throws InterruptedException if the thread gets interrupted. * @throws PubSubException.NotALeafNodeException if one of the PubSub nodes is not a {@link LeafNode}. * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error. * @throws SmackException.NotConnectedException if we are not connected. * @throws SmackException.NoResponseException if the server doesn't respond. * @throws IOException IO is dangerous. * @throws InvalidAlgorithmParameterException if illegal algorithm parameters are used for key generation. * @throws SmackException.NotLoggedInException if we are not logged in. * @throws PGPException if something goes wrong during key loading/generating */ public void announceSupportAndPublish() throws NoSuchAlgorithmException, NoSuchProviderException, InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException, IOException, InvalidAlgorithmParameterException, SmackException.NotLoggedInException, PGPException { throwIfNoProviderSet(); throwIfNotAuthenticated(); OpenPgpV4Fingerprint primaryFingerprint = getOurFingerprint(); if (primaryFingerprint == null) { primaryFingerprint = generateAndImportKeyPair(getJidOrThrow()); } // Create element PubkeyElement pubkeyElement; try { pubkeyElement = createPubkeyElement(getJidOrThrow(), primaryFingerprint, new Date()); } catch (MissingOpenPgpKeyException e) { throw new AssertionError("Cannot publish our public key, since it is missing (MUST NOT happen!)"); } // publish it publishPublicKey(pepManager, pubkeyElement, primaryFingerprint); // Subscribe to public key changes pepManager.addPepEventListener(PEP_NODE_PUBLIC_KEYS, PublicKeysListElement.class, pepPublicKeyListElementListener); ServiceDiscoveryManager.getInstanceFor(connection()) .addFeature(PEP_NODE_PUBLIC_KEYS_NOTIFY); } /** * Generate a fresh OpenPGP key pair and import it. * * @param ourJid our {@link BareJid}. * @return {@link OpenPgpV4Fingerprint} of the generated key. * @throws NoSuchAlgorithmException if the JVM doesn't support one of the used algorithms. * @throws InvalidAlgorithmParameterException if the used algorithm parameters are invalid. * @throws NoSuchProviderException if we are missing a cryptographic provider. * @throws PGPException PGP is brittle. * @throws IOException IO is dangerous. */ public OpenPgpV4Fingerprint generateAndImportKeyPair(BareJid ourJid) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchProviderException, PGPException, IOException { throwIfNoProviderSet(); OpenPgpStore store = provider.getStore(); PGPKeyRing keys = store.generateKeyRing(ourJid); try { store.importSecretKey(ourJid, keys.getSecretKeys()); store.importPublicKey(ourJid, keys.getPublicKeys()); } catch (MissingUserIdOnKeyException e) { // This should never throw, since we set our jid literally one line above this comment. throw new AssertionError(e); } OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(keys.getSecretKeys()); store.setTrust(ourJid, fingerprint, OpenPgpTrustStore.Trust.trusted); return fingerprint; } /** * Return the upper-case hex encoded OpenPGP v4 fingerprint of our key pair. * * @return fingerprint. * @throws SmackException.NotLoggedInException in case we are not logged in. * @throws IOException IO is dangerous. * @throws PGPException PGP is brittle. */ public OpenPgpV4Fingerprint getOurFingerprint() throws SmackException.NotLoggedInException, IOException, PGPException { return getOpenPgpSelf().getSigningKeyFingerprint(); } /** * Return an OpenPGP capable contact. * This object can be used as an entry point to OpenPGP related API. * * @param jid {@link BareJid} of the contact. * @return {@link OpenPgpContact}. */ public OpenPgpContact getOpenPgpContact(EntityBareJid jid) { throwIfNoProviderSet(); return provider.getStore().getOpenPgpContact(jid); } /** * Return true, if we have a secret key available, otherwise false. * * @return true if secret key available * * @throws SmackException.NotLoggedInException If we are not logged in (we need to know our jid in order to look up * our keys in the key store. * @throws PGPException in case the keys in the store are damaged somehow. * @throws IOException IO is dangerous. */ public boolean hasSecretKeysAvailable() throws SmackException.NotLoggedInException, PGPException, IOException { throwIfNoProviderSet(); return getOpenPgpSelf().hasSecretKeyAvailable(); } /** * Determine, if we can sync secret keys using private PEP nodes as described in the XEP. * Requirements on the server side are support for PEP and support for the whitelist access model of PubSub. * * @see XEP-0373 §5 * * @param connection XMPP connection * @return true, if the server supports secret key backups, otherwise false. * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error. * @throws SmackException.NotConnectedException if we are not connected. * @throws InterruptedException if the thread is interrupted. * @throws SmackException.NoResponseException if the server doesn't respond. */ public static boolean serverSupportsSecretKeyBackups(XMPPConnection connection) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { return ServiceDiscoveryManager.getInstanceFor(connection) .serverSupportsFeature(PubSubFeature.access_whitelist.toString()); } /** * Remove the metadata listener. This method is mainly used in tests. */ public void stopMetadataListener() { pepManager.removePepEventListener(pepPublicKeyListElementListener); } /** * Upload the encrypted secret key to a private PEP node. * * @see XEP-0373 §5 * * @param displayCodeCallback callback, which will receive the backup password used to encrypt the secret key. * @param selectKeyCallback callback, which will receive the users choice of which keys will be backed up. * @throws InterruptedException if the thread is interrupted. * @throws PubSubException.NotALeafNodeException if the private node is not a {@link LeafNode}. * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error. * @throws SmackException.NotConnectedException if we are not connected. * @throws SmackException.NoResponseException if the server doesn't respond. * @throws SmackException.NotLoggedInException if we are not logged in. * @throws IOException IO is dangerous. * @throws SmackException.FeatureNotSupportedException if the server doesn't support the PubSub whitelist access model. * @throws PGPException PGP is brittle * @throws MissingOpenPgpKeyException in case we have no OpenPGP key pair to back up. */ public void backupSecretKeyToServer(DisplayBackupCodeCallback displayCodeCallback, SecretKeyBackupSelectionCallback selectKeyCallback) throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException, SmackException.NotLoggedInException, IOException, SmackException.FeatureNotSupportedException, PGPException, MissingOpenPgpKeyException { throwIfNoProviderSet(); throwIfNotAuthenticated(); BareJid ownJid = connection().getUser().asBareJid(); String backupCode = SecretKeyBackupHelper.generateBackupPassword(); PGPSecretKeyRingCollection secretKeyRings = provider.getStore().getSecretKeysOf(ownJid); Set availableKeyPairs = new HashSet<>(); for (PGPSecretKeyRing ring : secretKeyRings) { availableKeyPairs.add(new OpenPgpV4Fingerprint(ring)); } Set selectedKeyPairs = selectKeyCallback.selectKeysToBackup(availableKeyPairs); SecretkeyElement secretKey = SecretKeyBackupHelper.createSecretkeyElement(provider, ownJid, selectedKeyPairs, backupCode); OpenPgpPubSubUtil.depositSecretKey(connection(), secretKey); displayCodeCallback.displayBackupCode(backupCode); } /** * Delete the private {@link LeafNode} containing our secret key backup. * * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error. * @throws SmackException.NotConnectedException if we are not connected. * @throws InterruptedException if the thread gets interrupted. * @throws SmackException.NoResponseException if the server doesn't respond. * @throws SmackException.NotLoggedInException if we are not logged in. */ public void deleteSecretKeyServerBackup() throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, SmackException.NotLoggedInException { throwIfNotAuthenticated(); OpenPgpPubSubUtil.deleteSecretKeyNode(pepManager); } /** * Fetch a secret key backup from the server and try to restore a selected secret key from it. * * @param codeCallback callback for prompting the user to provide the secret backup code. * @return fingerprint of the restored secret key * * @throws InterruptedException if the thread gets interrupted. * @throws PubSubException.NotALeafNodeException if the private node is not a {@link LeafNode}. * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error. * @throws SmackException.NotConnectedException if we are not connected. * @throws SmackException.NoResponseException if the server doesn't respond. * @throws InvalidBackupCodeException if the user-provided backup code is invalid. * @throws SmackException.NotLoggedInException if we are not logged in * @throws IOException IO is dangerous * @throws MissingUserIdOnKeyException if the key that is to be imported is missing a user-id with our jid * @throws NoBackupFoundException if no secret key backup has been found * @throws PGPException in case the restored secret key is damaged. */ public OpenPgpV4Fingerprint restoreSecretKeyServerBackup(AskForBackupCodeCallback codeCallback) throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException, InvalidBackupCodeException, SmackException.NotLoggedInException, IOException, MissingUserIdOnKeyException, NoBackupFoundException, PGPException { throwIfNoProviderSet(); throwIfNotAuthenticated(); SecretkeyElement backup = OpenPgpPubSubUtil.fetchSecretKey(pepManager); if (backup == null) { throw new NoBackupFoundException(); } String backupCode = codeCallback.askForBackupCode(); PGPSecretKeyRing secretKeys = SecretKeyBackupHelper.restoreSecretKeyBackup(backup, backupCode); provider.getStore().importSecretKey(getJidOrThrow(), secretKeys); provider.getStore().importPublicKey(getJidOrThrow(), BCUtil.publicKeyRingFromSecretKeyRing(secretKeys)); ByteArrayOutputStream buffer = new ByteArrayOutputStream(2048); for (PGPSecretKey sk : secretKeys) { PGPPublicKey pk = sk.getPublicKey(); if (pk != null) pk.encode(buffer); } PGPPublicKeyRing publicKeys = new PGPPublicKeyRing(buffer.toByteArray(), new BcKeyFingerprintCalculator()); provider.getStore().importPublicKey(getJidOrThrow(), publicKeys); return new OpenPgpV4Fingerprint(secretKeys); } /* Private stuff. */ private void processPublicKeysListElement(BareJid contact, PublicKeysListElement listElement) { OpenPgpContact openPgpContact = getOpenPgpContact(contact.asEntityBareJidIfPossible()); try { openPgpContact.updateKeys(connection(), listElement); } catch (Exception e) { LOGGER.log(Level.WARNING, "Could not update contacts keys", e); } } /** * Decrypt and or verify an {@link OpenPgpElement} and return the decrypted {@link OpenPgpMessage}. * * @param element {@link OpenPgpElement} containing the message. * @param sender {@link OpenPgpContact} who sent the message. * * @return decrypted and/or verified message * * @throws SmackException.NotLoggedInException in case we aren't logged in (we need to know our jid) * @throws IOException IO error (reading keys, streams etc) * @throws PGPException in case of an PGP error */ public OpenPgpMessage decryptOpenPgpElement(OpenPgpElement element, OpenPgpContact sender) throws SmackException.NotLoggedInException, IOException, PGPException { return provider.decryptAndOrVerify(element, getOpenPgpSelf(), sender); } private void incomingChatMessageListener(final EntityBareJid from, final Message message, Chat chat) { Async.go(new Runnable() { @Override public void run() { OpenPgpElement element = message.getExtension(OpenPgpElement.class); if (element == null) { // Message does not contain an OpenPgpElement -> discard return; } OpenPgpContact contact = getOpenPgpContact(from); OpenPgpMessage decrypted = null; OpenPgpContentElement contentElement = null; try { decrypted = decryptOpenPgpElement(element, contact); contentElement = decrypted.getOpenPgpContentElement(); } catch (PGPException e) { LOGGER.log(Level.WARNING, "Could not decrypt incoming OpenPGP encrypted message", e); } catch (XmlPullParserException | IOException e) { LOGGER.log(Level.WARNING, "Invalid XML content of incoming OpenPGP encrypted message", e); } catch (SmackException.NotLoggedInException e) { LOGGER.log(Level.WARNING, "Cannot determine our JID, since we are not logged in.", e); } if (contentElement instanceof SigncryptElement) { for (SigncryptElementReceivedListener l : signcryptElementReceivedListeners) { l.signcryptElementReceived(contact, message, (SigncryptElement) contentElement, decrypted.getMetadata()); } return; } if (contentElement instanceof SignElement) { for (SignElementReceivedListener l : signElementReceivedListeners) { l.signElementReceived(contact, message, (SignElement) contentElement, decrypted.getMetadata()); } return; } if (contentElement instanceof CryptElement) { for (CryptElementReceivedListener l : cryptElementReceivedListeners) { l.cryptElementReceived(contact, message, (CryptElement) contentElement, decrypted.getMetadata()); } return; } else { throw new AssertionError("Invalid element received: " + contentElement.getClass().getName()); } } }); } /** * Create a {@link PubkeyElement} which contains the OpenPGP public key of {@code owner} which belongs to * the {@link OpenPgpV4Fingerprint} {@code fingerprint}. * * @param owner owner of the public key * @param fingerprint fingerprint of the key * @param date date of creation of the element * @return {@link PubkeyElement} containing the key * * @throws MissingOpenPgpKeyException if the public key notated by the fingerprint cannot be found */ private PubkeyElement createPubkeyElement(BareJid owner, OpenPgpV4Fingerprint fingerprint, Date date) throws MissingOpenPgpKeyException, IOException, PGPException { PGPPublicKeyRing ring = provider.getStore().getPublicKeyRing(owner, fingerprint); if (ring != null) { byte[] keyBytes = ring.getEncoded(true); return createPubkeyElement(keyBytes, date); } throw new MissingOpenPgpKeyException(owner, fingerprint); } /** * Create a {@link PubkeyElement} which contains the given {@code data} base64 encoded. * * @param bytes byte representation of an OpenPGP public key * @param date date of creation of the element * @return {@link PubkeyElement} containing the key */ private static PubkeyElement createPubkeyElement(byte[] bytes, Date date) { String base64EncodedOpenPgpPubKey = Base64.encodeToString(bytes); return new PubkeyElement(new PubkeyElement.PubkeyDataElement(base64EncodedOpenPgpPubKey), date); } /** * Register a {@link SigncryptElementReceivedListener} on the {@link OpenPgpManager}. * That listener will get informed whenever a {@link SigncryptElement} has been received and successfully decrypted. * * Note: This method is not intended for clients to listen for incoming {@link SigncryptElement}s. * Instead its purpose is to allow easy extension of XEP-0373 for custom OpenPGP profiles such as * OpenPGP for XMPP: Instant Messaging. * * @param listener listener that gets registered */ public void registerSigncryptReceivedListener(SigncryptElementReceivedListener listener) { signcryptElementReceivedListeners.add(listener); } /** * Unregister a prior registered {@link SigncryptElementReceivedListener}. That listener will no longer get * informed about incoming decrypted {@link SigncryptElement}s. * * @param listener listener that gets unregistered */ void unregisterSigncryptElementReceivedListener(SigncryptElementReceivedListener listener) { signcryptElementReceivedListeners.remove(listener); } /** * Register a {@link SignElementReceivedListener} on the {@link OpenPgpManager}. * That listener will get informed whenever a {@link SignElement} has been received and successfully verified. * * Note: This method is not intended for clients to listen for incoming {@link SignElement}s. * Instead its purpose is to allow easy extension of XEP-0373 for custom OpenPGP profiles such as * OpenPGP for XMPP: Instant Messaging. * * @param listener listener that gets registered */ void registerSignElementReceivedListener(SignElementReceivedListener listener) { signElementReceivedListeners.add(listener); } /** * Unregister a prior registered {@link SignElementReceivedListener}. That listener will no longer get * informed about incoming decrypted {@link SignElement}s. * * @param listener listener that gets unregistered */ void unregisterSignElementReceivedListener(SignElementReceivedListener listener) { signElementReceivedListeners.remove(listener); } /** * Register a {@link CryptElementReceivedListener} on the {@link OpenPgpManager}. * That listener will get informed whenever a {@link CryptElement} has been received and successfully decrypted. * * Note: This method is not intended for clients to listen for incoming {@link CryptElement}s. * Instead its purpose is to allow easy extension of XEP-0373 for custom OpenPGP profiles such as * OpenPGP for XMPP: Instant Messaging. * * @param listener listener that gets registered */ void registerCryptElementReceivedListener(CryptElementReceivedListener listener) { cryptElementReceivedListeners.add(listener); } /** * Unregister a prior registered {@link CryptElementReceivedListener}. That listener will no longer get * informed about incoming decrypted {@link CryptElement}s. * * @param listener listener that gets unregistered */ void unregisterCryptElementReceivedListener(CryptElementReceivedListener listener) { cryptElementReceivedListeners.remove(listener); } /** * Throw an {@link IllegalStateException} if no {@link OpenPgpProvider} is set. * The OpenPgpProvider is used to process information related to RFC-4880. */ private void throwIfNoProviderSet() { if (provider == null) { throw new IllegalStateException("No OpenPgpProvider set!"); } } /** * Throw a {@link org.jivesoftware.smack.SmackException.NotLoggedInException} if the {@link XMPPConnection} of this * manager is not authenticated at this point. * * @throws SmackException.NotLoggedInException if we are not authenticated */ private void throwIfNotAuthenticated() throws SmackException.NotLoggedInException { if (!connection().isAuthenticated()) { throw new SmackException.NotLoggedInException(); } } }