diff --git a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleOpenPgpProvider.java b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleOpenPgpProvider.java index 32fc44683..d28e54b7f 100644 --- a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleOpenPgpProvider.java +++ b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleOpenPgpProvider.java @@ -34,6 +34,7 @@ import org.jivesoftware.smackx.ox.OpenPgpProvider; 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.exception.CorruptedOpenPgpKeyException; import name.neuhalfen.projects.crypto.bouncycastle.openpgp.BouncyGPG; import name.neuhalfen.projects.crypto.bouncycastle.openpgp.algorithms.PublicKeySize; @@ -68,24 +69,32 @@ public class BouncyCastleOpenPgpProvider implements OpenPgpProvider { } @Override - public PubkeyElement createPubkeyElement() throws IOException, PGPException { - PGPPublicKey pubKey = ourKeys.getPublicKeyRings().getPublicKey(ourKeyId); - PubkeyElement.PubkeyDataElement dataElement = new PubkeyElement.PubkeyDataElement( - Base64.encode(pubKey.getEncoded())); - return new PubkeyElement(dataElement, new Date()); + public PubkeyElement createPubkeyElement() throws CorruptedOpenPgpKeyException { + try { + PGPPublicKey pubKey = ourKeys.getPublicKeyRings().getPublicKey(ourKeyId); + PubkeyElement.PubkeyDataElement dataElement = new PubkeyElement.PubkeyDataElement( + Base64.encode(pubKey.getEncoded())); + return new PubkeyElement(dataElement, new Date()); + } catch (PGPException | IOException e) { + throw new CorruptedOpenPgpKeyException(e); + } } @Override - public void processPubkeyElement(PubkeyElement element, BareJid jid) throws IOException, PGPException { + public void processPubkeyElement(PubkeyElement element, BareJid jid) throws CorruptedOpenPgpKeyException { byte[] decoded = Base64.decode(element.getDataElement().getB64Data()); - InMemoryKeyring contactsKeyring = theirKeys.get(jid); - if (contactsKeyring == null) { - contactsKeyring = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys()); - theirKeys.put(jid, contactsKeyring); - } + try { + InMemoryKeyring contactsKeyring = theirKeys.get(jid); + if (contactsKeyring == null) { + contactsKeyring = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys()); + theirKeys.put(jid, contactsKeyring); + } - contactsKeyring.addPublicKey(decoded); + contactsKeyring.addPublicKey(decoded); + } catch (IOException | PGPException e) { + throw new CorruptedOpenPgpKeyException(e); + } } @Override @@ -188,10 +197,14 @@ public class BouncyCastleOpenPgpProvider implements OpenPgpProvider { } @Override - public String getFingerprint() throws IOException, PGPException { - return new String(Hex.encode(ourKeys.getKeyFingerPrintCalculator() - .calculateFingerprint(ourKeys.getPublicKeyRings().getPublicKey(ourKeyId) - .getPublicKeyPacket())), Charset.forName("UTF-8")).toUpperCase(); + public String getFingerprint() throws CorruptedOpenPgpKeyException { + try { + return new String(Hex.encode(ourKeys.getKeyFingerPrintCalculator() + .calculateFingerprint(ourKeys.getPublicKeyRings().getPublicKey(ourKeyId) + .getPublicKeyPacket())), Charset.forName("UTF-8")).toUpperCase(); + } catch (IOException | PGPException e) { + throw new CorruptedOpenPgpKeyException(e); + } } public static PGPKeyRingGenerator generateKey(BareJid owner) throws NoSuchAlgorithmException, PGPException { diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java index 74ea57acc..652487995 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java @@ -32,6 +32,7 @@ import org.jivesoftware.smack.util.Async; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.ox.element.PubkeyElement; import org.jivesoftware.smackx.ox.element.PublicKeysListElement; +import org.jivesoftware.smackx.ox.exception.CorruptedOpenPgpKeyException; import org.jivesoftware.smackx.pep.PEPListener; import org.jivesoftware.smackx.pep.PEPManager; import org.jivesoftware.smackx.pubsub.EventElement; @@ -49,16 +50,45 @@ public final class OpenPgpManager extends Manager { private static final Logger LOGGER = Logger.getLogger(OpenPgpManager.class.getName()); + /** + * Name of the OX metadata node. + * + * @see XEP-0373 §4.2. + */ public static final String PEP_NODE_PUBLIC_KEYS = "urn:xmpp:openpgp:0:public-keys"; + + /** + * Feature to be announced using the {@link ServiceDiscoveryManager} to subscribe to the OX metadata node. + * + * @see XEP-0373 §4.4. + */ public static final String PEP_NODE_PUBLIC_KEYS_NOTIFY = PEP_NODE_PUBLIC_KEYS + "+notify"; + /** + * Name of the OX public key node, which contains the key with id {@code id}. + * + * @param id upper case hex encoded OpenPGP v4 fingerprint of the key. + * @return PEP node name. + */ public static String PEP_NODE_PUBLIC_KEY(String id) { return PEP_NODE_PUBLIC_KEYS + ":" + id; } + /** + * 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 constructor to avoid instantiation without putting the object into {@code INSTANCES}. + * + * @param connection xmpp connection. + */ private OpenPgpManager(XMPPConnection connection) { super(connection); @@ -68,6 +98,12 @@ public final class OpenPgpManager extends Manager { .addFeature(PEP_NODE_PUBLIC_KEYS_NOTIFY); } + /** + * Get the instance of the {@link OpenPgpManager} which belongs to the {@code connection}. + * + * @param connection xmpp connection. + * @return instance of the manager. + */ public static OpenPgpManager getInstanceFor(XMPPConnection connection) { OpenPgpManager manager = INSTANCES.get(connection); if (manager == null) { @@ -77,11 +113,32 @@ public final class OpenPgpManager extends Manager { return manager; } + /** + * 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 void publishPublicKey() throws Exception { + /** + * Publish the users OpenPGP public key to the public key node if necessary. + * Also announce the key to other users by updating the metadata node. + * + * @see XEP-0373 §4.1. + * + * @throws CorruptedOpenPgpKeyException if our OpenPGP key is corrupted and for that reason cannot be serialized. + * @throws InterruptedException + * @throws PubSubException.NotALeafNodeException + * @throws XMPPException.XMPPErrorException + * @throws SmackException.NotConnectedException + * @throws SmackException.NoResponseException + */ + public void publishPublicKey() + throws CorruptedOpenPgpKeyException, InterruptedException, PubSubException.NotALeafNodeException, + XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException { ensureProviderIsSet(); PubkeyElement pubkeyElement = provider.createPubkeyElement(); @@ -96,9 +153,11 @@ public final class OpenPgpManager extends Manager { if (items.isEmpty()) { LOGGER.log(Level.FINE, "Node " + keyNodeName + " is empty. Publish."); keyNode.publish(new PayloadItem<>(pubkeyElement)); + } else { + LOGGER.log(Level.FINE, "Node " + keyNodeName + " already contains key. Skip."); } - // Publish ID to metadata node + // Fetch IDs from metadata node LeafNode metadataNode = pm.getOrCreateLeafNode(PEP_NODE_PUBLIC_KEYS); List> metadataItems = metadataNode.getItems(1); @@ -112,9 +171,22 @@ public final class OpenPgpManager extends Manager { } builder.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(fingerprint, new Date())); + // Publish IDs to metadata node metadataNode.publish(new PayloadItem<>(builder.build())); } + /** + * Consult the public key metadata node and fetch a list of all of our published OpenPGP public keys. + * TODO: Add @see which points to the (for now missing) respective example in XEP-0373. + * + * @return content of our metadata node. + * @throws InterruptedException + * @throws PubSubException.NotALeafNodeException + * @throws SmackException.NoResponseException + * @throws SmackException.NotConnectedException + * @throws XMPPException.XMPPErrorException + * @throws PubSubException.NotAPubSubNodeException + */ public PublicKeysListElement fetchPubkeysList() throws InterruptedException, PubSubException.NotALeafNodeException, SmackException.NoResponseException, SmackException.NotConnectedException, XMPPException.XMPPErrorException, @@ -122,18 +194,24 @@ public final class OpenPgpManager extends Manager { return fetchPubkeysList(connection().getUser().asBareJid()); } - public void deletePubkeysListNode() - throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, - SmackException.NoResponseException { - PubSubManager pm = PubSubManager.getInstance(connection(), connection().getUser().asBareJid()); - pm.deleteNode(PEP_NODE_PUBLIC_KEYS); - } - - public PublicKeysListElement fetchPubkeysList(BareJid jid) + /** + * Consult the public key metadata node of {@code contact} to fetch the list of their published OpenPGP public keys. + * TODO: Add @see which points to the (for now missing) respective example in XEP-0373. + * + * @param contact {@link BareJid} of the user we want to fetch the list from. + * @return content of {@code contact}'s metadata node. + * @throws InterruptedException + * @throws PubSubException.NotALeafNodeException + * @throws SmackException.NoResponseException + * @throws SmackException.NotConnectedException + * @throws XMPPException.XMPPErrorException + * @throws PubSubException.NotAPubSubNodeException + */ + public PublicKeysListElement fetchPubkeysList(BareJid contact) throws InterruptedException, PubSubException.NotALeafNodeException, SmackException.NoResponseException, SmackException.NotConnectedException, XMPPException.XMPPErrorException, PubSubException.NotAPubSubNodeException { - PubSubManager pm = PubSubManager.getInstance(connection(), jid); + PubSubManager pm = PubSubManager.getInstance(connection(), contact); LeafNode node = pm.getLeafNode(PEP_NODE_PUBLIC_KEYS); List> list = node.getItems(1); @@ -145,11 +223,41 @@ public final class OpenPgpManager extends Manager { return list.get(0).getPayload(); } - public PubkeyElement fetchPubkey(BareJid jid, String v4_fingerprint) + /** + * Delete our metadata node. + * + * @throws XMPPException.XMPPErrorException + * @throws SmackException.NotConnectedException + * @throws InterruptedException + * @throws SmackException.NoResponseException + */ + public void deletePubkeysListNode() + throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, + SmackException.NoResponseException { + PubSubManager pm = PubSubManager.getInstance(connection(), connection().getUser().asBareJid()); + pm.deleteNode(PEP_NODE_PUBLIC_KEYS); + } + + /** + * Fetch the OpenPGP public key of a {@code contact}, identified by its OpenPGP {@code v4_fingerprint}. + * + * @see XEP-0373 §4.3. + * + * @param contact {@link BareJid} of the contact we want to fetch a key from. + * @param v4_fingerprint upper case, hex encoded v4 fingerprint of the contacts key. + * @return {@link PubkeyElement} containing the requested public key. + * @throws InterruptedException + * @throws PubSubException.NotALeafNodeException + * @throws SmackException.NoResponseException + * @throws SmackException.NotConnectedException + * @throws XMPPException.XMPPErrorException + * @throws PubSubException.NotAPubSubNodeException + */ + public PubkeyElement fetchPubkey(BareJid contact, String v4_fingerprint) throws InterruptedException, PubSubException.NotALeafNodeException, SmackException.NoResponseException, SmackException.NotConnectedException, XMPPException.XMPPErrorException, PubSubException.NotAPubSubNodeException { - PubSubManager pm = PubSubManager.getInstance(connection(), jid); + PubSubManager pm = PubSubManager.getInstance(connection(), contact); LeafNode node = pm.getLeafNode(PEP_NODE_PUBLIC_KEY(v4_fingerprint)); List> list = node.getItems(1); @@ -161,6 +269,9 @@ public final class OpenPgpManager extends Manager { return list.get(0).getPayload(); } + /** + * TODO: Implement and document. + */ public void depositSecretKey() { ensureProviderIsSet(); // Create key backup by appending serialized unencrypted secret keys. @@ -169,13 +280,20 @@ public final class OpenPgpManager extends Manager { // Display the backup key to the user } - public String getOurFingerprint() throws Exception { + /** + * Return the upper-case hex encoded OpenPGP v4 fingerprint of our key pair. + * + * @return fingerprint. + * @throws CorruptedOpenPgpKeyException if for some reason we cannot determine our fingerprint. + */ + public String getOurFingerprint() throws CorruptedOpenPgpKeyException { ensureProviderIsSet(); return provider.getFingerprint(); } /** * Throw an {@link IllegalStateException} if no {@link OpenPgpProvider} is set. + * The OpenPgpProvider is used to process information related to RFC-4880. */ private void ensureProviderIsSet() { if (provider == null) { @@ -183,6 +301,18 @@ public final class OpenPgpManager extends Manager { } } + /** + * 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 for more information. + * + * @return + * @throws XMPPException.XMPPErrorException + * @throws SmackException.NotConnectedException + * @throws InterruptedException + * @throws SmackException.NoResponseException + */ public boolean canSyncSecretKey() throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { @@ -192,9 +322,14 @@ public final class OpenPgpManager extends Manager { return pep && whitelist; } + /** + * {@link PEPListener} that listens for changes to the OX public keys metadata node. + * + * @see XEP-0373 §4.4. + */ private final PEPListener metadataListener = new PEPListener() { @Override - public void eventReceived(EntityBareJid from, final EventElement event, Message message) { + public void eventReceived(final EntityBareJid from, final EventElement event, Message message) { if (PEP_NODE_PUBLIC_KEYS.equals(event.getEvent().getNode())) { LOGGER.log(Level.INFO, "Received OpenPGP metadata update from " + from); Async.go(new Runnable() { @@ -204,6 +339,11 @@ public final class OpenPgpManager extends Manager { PayloadItem payload = (PayloadItem) items.getItems().get(0); PublicKeysListElement listElement = (PublicKeysListElement) payload.getPayload(); + try { + provider.processPublicKeysListElement(listElement, from.asBareJid()); + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Error processing OpenPGP metadata update from " + from, e); + } } }, "ProcessOXPublicKey"); } diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpProvider.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpProvider.java index 55881e945..9cc801c08 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpProvider.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpProvider.java @@ -22,6 +22,7 @@ import java.util.Set; 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.exception.CorruptedOpenPgpKeyException; import org.jxmpp.jid.BareJid; @@ -35,9 +36,9 @@ public interface OpenPgpProvider { OpenPgpElement encrypt(InputStream inputStream, Set recipients) throws Exception; - PubkeyElement createPubkeyElement() throws Exception; + PubkeyElement createPubkeyElement() throws CorruptedOpenPgpKeyException; - void processPubkeyElement(PubkeyElement element, BareJid from) throws Exception; + void processPubkeyElement(PubkeyElement element, BareJid from) throws CorruptedOpenPgpKeyException; void processPublicKeysListElement(PublicKeysListElement listElement, BareJid from) throws Exception; @@ -45,7 +46,7 @@ public interface OpenPgpProvider { * Return the OpenPGP v4-fingerprint of our key in hexadecimal upper case. * * @return fingerprint - * @throws Exception + * @throws CorruptedOpenPgpKeyException if for some reason the fingerprint cannot be derived from the key pair. */ - String getFingerprint() throws Exception; + String getFingerprint() throws CorruptedOpenPgpKeyException; } diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/CorruptedOpenPgpKeyException.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/CorruptedOpenPgpKeyException.java similarity index 85% rename from smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/CorruptedOpenPgpKeyException.java rename to smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/CorruptedOpenPgpKeyException.java index b383f3572..113266af8 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/CorruptedOpenPgpKeyException.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/CorruptedOpenPgpKeyException.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jivesoftware.smackx.ox; +package org.jivesoftware.smackx.ox.exception; public class CorruptedOpenPgpKeyException extends Exception { @@ -23,4 +23,8 @@ public class CorruptedOpenPgpKeyException extends Exception { public CorruptedOpenPgpKeyException() { super(); } + + public CorruptedOpenPgpKeyException(Exception cause) { + super(cause); + } } diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/package-info.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/package-info.java new file mode 100644 index 000000000..66fe94674 --- /dev/null +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/package-info.java @@ -0,0 +1,20 @@ +/** + * + * Copyright 2017 Florian Schmaus. + * + * 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. + */ +/** + * Exceptions for XEP-0373: OpenPGP for XMPP. + */ +package org.jivesoftware.smackx.ox.exception;