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;