1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2025-12-10 04:51:08 +01:00

Warning: Construction Site!

This commit is contained in:
Paul Schaub 2018-05-28 00:58:13 +02:00
parent 365a4d20d0
commit 2b7738cc9c
34 changed files with 1635 additions and 802 deletions

View file

@ -0,0 +1,51 @@
/**
*
* Copyright 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 java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jxmpp.jid.BareJid;
public class OXInstantMessagingManager extends Manager {
private static final Map<XMPPConnection, OXInstantMessagingManager> INSTANCES = new WeakHashMap<>();
private final OpenPgpManager openPgpManager;
private OXInstantMessagingManager(XMPPConnection connection) {
super(connection);
this.openPgpManager = OpenPgpManager.getInstanceFor(connection);
}
public static OXInstantMessagingManager getInstanceFor(XMPPConnection connection) {
OXInstantMessagingManager manager = INSTANCES.get(connection);
if (manager == null) {
manager = new OXInstantMessagingManager(connection);
INSTANCES.put(connection, manager);
}
return manager;
}
public void send(List<ExtensionElement> messageContent, BareJid recipient) {
}
}

View file

@ -16,9 +16,10 @@
*/
package org.jivesoftware.smackx.ox;
import static org.jivesoftware.smackx.ox.PubSubDelegate.PEP_NODE_PUBLIC_KEYS;
import static org.jivesoftware.smackx.ox.PubSubDelegate.PEP_NODE_PUBLIC_KEYS_NOTIFY;
import java.security.SecureRandom;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Level;
@ -33,56 +34,26 @@ import org.jivesoftware.smack.util.Async;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.ox.callback.AskForBackupCodeCallback;
import org.jivesoftware.smackx.ox.callback.DisplayBackupCodeCallback;
import org.jivesoftware.smackx.ox.element.PubkeyElement;
import org.jivesoftware.smackx.ox.callback.SecretKeyRestoreSelectionCallback;
import org.jivesoftware.smackx.ox.element.PublicKeysListElement;
import org.jivesoftware.smackx.ox.element.SecretkeyElement;
import org.jivesoftware.smackx.ox.exception.CorruptedOpenPgpKeyException;
import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException;
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException;
import org.jivesoftware.smackx.pep.PEPListener;
import org.jivesoftware.smackx.pep.PEPManager;
import org.jivesoftware.smackx.pubsub.EventElement;
import org.jivesoftware.smackx.pubsub.Item;
import org.jivesoftware.smackx.pubsub.ItemsExtension;
import org.jivesoftware.smackx.pubsub.LeafNode;
import org.jivesoftware.smackx.pubsub.PayloadItem;
import org.jivesoftware.smackx.pubsub.PubSubException;
import org.jivesoftware.smackx.pubsub.PubSubManager;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.EntityBareJid;
public final class OpenPgpManager extends Manager {
private static final Logger LOGGER = Logger.getLogger(OpenPgpManager.class.getName());
/**
* Name of the OX metadata node.
*
* @see <a href="https://xmpp.org/extensions/xep-0373.html#announcing-pubkey-list">XEP-0373 §4.2</a>
*/
public static final String PEP_NODE_PUBLIC_KEYS = "urn:xmpp:openpgp:0:public-keys";
/**
* Name of the OX secret key node.
*/
public static final String PEP_NODE_SECRET_KEY = "urn:xmpp:openpgp:0:secret-key";
/**
* Feature to be announced using the {@link ServiceDiscoveryManager} to subscribe to the OX metadata node.
*
* @see <a href="https://xmpp.org/extensions/xep-0373.html#pubsub-notifications">XEP-0373 §4.4</a>
*/
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.
*/
@ -132,204 +103,15 @@ public final class OpenPgpManager extends Manager {
this.provider = provider;
}
/**
* 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 <a href="https://xmpp.org/extensions/xep-0373.html#annoucning-pubkey">XEP-0373 §4.1</a>
*
* @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();
String fingerprint = provider.getFingerprint();
String keyNodeName = PEP_NODE_PUBLIC_KEY(fingerprint);
PubSubManager pm = PubSubManager.getInstance(connection(), connection().getUser().asBareJid());
// Check if key available at data node
// If not, publish key to data node
LeafNode keyNode = pm.getOrCreateLeafNode(keyNodeName);
List<Item> items = keyNode.getItems(1);
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.");
}
// Fetch IDs from metadata node
LeafNode metadataNode = pm.getOrCreateLeafNode(PEP_NODE_PUBLIC_KEYS);
List<PayloadItem<PublicKeysListElement>> metadataItems = metadataNode.getItems(1);
PublicKeysListElement.Builder builder = PublicKeysListElement.builder();
if (!metadataItems.isEmpty() && metadataItems.get(0).getPayload() != null) {
// Add old entries back to list.
PublicKeysListElement publishedList = metadataItems.get(0).getPayload();
for (PublicKeysListElement.PubkeyMetadataElement meta : publishedList.getMetadata().values()) {
builder.addMetadata(meta);
}
}
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,
PubSubException.NotAPubSubNodeException {
return fetchPubkeysList(connection().getUser().asBareJid());
}
/**
* 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(), contact);
LeafNode node = pm.getLeafNode(PEP_NODE_PUBLIC_KEYS);
List<PayloadItem<PublicKeysListElement>> list = node.getItems(1);
if (list.isEmpty()) {
return null;
}
return list.get(0).getPayload();
}
/**
* 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 <a href="https://xmpp.org/extensions/xep-0373.html#discover-pubkey">XEP-0373 §4.3</a>
*
* @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(), contact);
LeafNode node = pm.getLeafNode(PEP_NODE_PUBLIC_KEY(v4_fingerprint));
List<PayloadItem<PubkeyElement>> list = node.getItems(1);
if (list.isEmpty()) {
return null;
}
return list.get(0).getPayload();
}
/**
* TODO: Implement and document.
*/
public void depositSecretKey(DisplayBackupCodeCallback callback)
throws CorruptedOpenPgpKeyException, InterruptedException, PubSubException.NotALeafNodeException,
XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException {
ensureProviderIsSet();
String password = generateBackupPassword();
SecretkeyElement secretKeyElement = provider.createSecretkeyElement(password);
PubSubManager pm = PubSubManager.getInstance(connection());
LeafNode secretKeyNode = pm.getOrCreateLeafNode(PEP_NODE_SECRET_KEY);
PubSubHelper.whitelist(secretKeyNode);
secretKeyNode.publish(new PayloadItem<>(secretKeyElement));
callback.displayBackupCode(password);
}
public void fetchSecretKey(AskForBackupCodeCallback callback)
throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, SmackException.NoResponseException, CorruptedOpenPgpKeyException {
PubSubManager pm = PubSubManager.getInstance(connection());
LeafNode secretKeyNode = pm.getOrCreateLeafNode(PEP_NODE_SECRET_KEY);
List<PayloadItem<SecretkeyElement>> list = secretKeyNode.getItems(1);
if (list.size() == 0) {
LOGGER.log(Level.INFO, "No secret key published!");
return;
}
SecretkeyElement secretkeyElement = list.get(0).getPayload();
provider.restoreSecretKeyElement(secretkeyElement, callback.askForBackupCode());
}
/**
* 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) {
throw new IllegalStateException("No OpenPgpProvider set!");
}
public OpenPgpV4Fingerprint getOurFingerprint() throws CorruptedOpenPgpKeyException {
throwIfNoProviderSet();
return provider.primaryOpenPgpKeyPairFingerprint();
}
/**
@ -338,13 +120,13 @@ public final class OpenPgpManager extends Manager {
*
* @see <a href="https://xmpp.org/extensions/xep-0373.html#synchro-pep">XEP-0373 §5</a>
*
* @return
* @return true, if the server supports secret key backups, otherwise false.
* @throws XMPPException.XMPPErrorException
* @throws SmackException.NotConnectedException
* @throws InterruptedException
* @throws SmackException.NoResponseException
*/
public boolean canSyncSecretKey()
public boolean serverSupportsSecretKeyBackups()
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException {
boolean pep = PEPManager.getInstanceFor(connection()).isSupported();
@ -353,6 +135,47 @@ public final class OpenPgpManager extends Manager {
return pep && whitelist;
}
/**
* Upload the encrypted secret key to a private PEP node.
*
* @see <a href="https://xmpp.org/extensions/xep-0373.html#synchro-pep">XEP-0373 §5</a>
*
* @param callback callback, which will receive the backup password used to encrypt the secret key.
* @throws CorruptedOpenPgpKeyException if the secret key is corrupted or can for some reason not be serialized.
* @throws InterruptedException
* @throws PubSubException.NotALeafNodeException
* @throws XMPPException.XMPPErrorException
* @throws SmackException.NotConnectedException
* @throws SmackException.NoResponseException
*/
public void backupSecretKeyToServer(DisplayBackupCodeCallback callback)
throws CorruptedOpenPgpKeyException, InterruptedException, PubSubException.NotALeafNodeException,
XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException,
MissingOpenPgpKeyPairException {
throwIfNoProviderSet();
String backupCode = generateBackupPassword();
SecretkeyElement secretKey = provider.createSecretkeyElement(null, backupCode); // TODO
PubSubDelegate.depositSecretKey(connection(), secretKey);
callback.displayBackupCode(backupCode);
}
public void deleteSecretKeyServerBackup()
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException {
PubSubDelegate.deleteSecretKeyNode(connection());
}
public void restoreSecretKeyServerBackup(AskForBackupCodeCallback codeCallback,
SecretKeyRestoreSelectionCallback selectionCallback)
throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, SmackException.NoResponseException, CorruptedOpenPgpKeyException,
InvalidBackupCodeException {
throwIfNoProviderSet();
SecretkeyElement backup = PubSubDelegate.fetchSecretKey(connection());
provider.restoreSecretKeyBackup(backup, codeCallback.askForBackupCode(), selectionCallback);
// TODO: catch InvalidBackupCodeException in order to prevent re-fetching the backup on next try.
}
/**
* {@link PEPListener} that listens for changes to the OX public keys metadata node.
*
@ -371,12 +194,12 @@ public final class OpenPgpManager extends Manager {
PublicKeysListElement listElement = (PublicKeysListElement) payload.getPayload();
try {
provider.processPublicKeysListElement(listElement, from.asBareJid());
provider.storePublicKeysList(connection(), listElement, from.asBareJid());
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Error processing OpenPGP metadata update from " + from, e);
LOGGER.log(Level.WARNING, "Error processing OpenPGP metadata update from " + from + ".", e);
}
}
}, "ProcessOXPublicKey");
}, "ProcessOXMetadata");
}
}
};
@ -387,7 +210,7 @@ public final class OpenPgpManager extends Manager {
* @see <a href="https://xmpp.org/extensions/xep-0373.html#sect-idm140425111347232">XEP-0373 §5.3</a>
* @return backup code
*/
private String generateBackupPassword() {
private static String generateBackupPassword() {
final String alphabet = "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ";
SecureRandom random = new SecureRandom();
StringBuilder code = new StringBuilder();
@ -408,4 +231,14 @@ public final class OpenPgpManager extends Manager {
}
return code.toString();
}
/**
* 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!");
}
}
}

View file

@ -17,20 +17,19 @@
package org.jivesoftware.smackx.ox;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Set;
import org.jivesoftware.smackx.ox.element.CryptElement;
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.CorruptedOpenPgpKeyException;
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException;
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException;
import org.jxmpp.jid.BareJid;
public interface OpenPgpProvider {
public interface OpenPgpProvider extends OpenPgpStore {
/**
* Sign and encrypt a {@link SigncryptElement} element for usage within the context of instant messaging.
@ -41,11 +40,19 @@ public interface OpenPgpProvider {
* @see <a href="https://xmpp.org/extensions/xep-0373.html#signcrypt">XEP-0373 §3</a>
* @see <a href="https://xmpp.org/extensions/xep-0374.html#openpgp-secured-im">XEP-0374 §2.1</a>
* @param element {@link SigncryptElement} which contains the content of the message as plaintext.
* @param recipients {@link Set} of {@link BareJid} of recipients.
* @param signingKey {@link OpenPgpV4Fingerprint} of the signing key.
* @param encryptionKeys {@link Set} containing all {@link OpenPgpV4Fingerprint}s of keys which will
* be able to decrypt the message.
* @return encrypted {@link OpenPgpElement} which contains the encrypted, encoded message.
* @throws Exception
* @throws MissingOpenPgpKeyPairException if the OpenPGP key pair with the given {@link OpenPgpV4Fingerprint}
* is not available.
* @throws MissingOpenPgpKeyPairException if any of the OpenPGP public keys whose {@link OpenPgpV4Fingerprint}
* is listed in {@code encryptionKeys} is not available.
*/
OpenPgpElement signAndEncrypt(SigncryptElement element, Set<BareJid> recipients) throws Exception;
OpenPgpElement signAndEncrypt(SigncryptElement element,
OpenPgpV4Fingerprint signingKey,
Set<OpenPgpV4Fingerprint> encryptionKeys)
throws MissingOpenPgpKeyPairException, MissingOpenPgpPublicKeyException;
/**
* Decrypt an incoming {@link OpenPgpElement} which must contain a {@link SigncryptElement} and verify
@ -53,110 +60,94 @@ public interface OpenPgpProvider {
*
* @see <a href="https://xmpp.org/extensions/xep-0374.html#openpgp-secured-im">XEP-0374 §2.1</a>
* @param element {@link OpenPgpElement} which contains an encrypted and signed {@link SigncryptElement}.
* @param sender {@link BareJid} of the user which sent the message. This is also the user who signed the message.
* @param sendersKeys {@link Set} of the senders {@link OpenPgpV4Fingerprint}s.
* It is required, that one of those keys was used for signing the message.
* @return decrypted {@link OpenPgpMessage} which contains the decrypted {@link SigncryptElement}.
* @throws Exception
* @throws MissingOpenPgpKeyPairException if we have no OpenPGP key pair to decrypt the message.
* @throws MissingOpenPgpPublicKeyException if we do not have the public OpenPGP key of the sender to
* verify the signature on the message.
*/
OpenPgpMessage decryptAndVerify(OpenPgpElement element, BareJid sender) throws Exception;
OpenPgpMessage decryptAndVerify(OpenPgpElement element, Set<OpenPgpV4Fingerprint> sendersKeys)
throws MissingOpenPgpKeyPairException, MissingOpenPgpPublicKeyException;
/**
* Sign a {@link SignElement} and pack it inside a {@link OpenPgpElement}.
* The resulting {@link OpenPgpElement} contains the {@link SignElement} signed and base64 encoded.
*
* <br>
* Note: DO NOT use this method in the context of instant messaging, as XEP-0374 forbids that.
*
* @see <a href="https://xmpp.org/extensions/xep-0373.html#exchange">XEP-0373 §3.1</a>
* @see <a href="https://xmpp.org/extensions/xep-0374.html#openpgp-secured-im">XEP-0374 §2.1</a>
* @param element {@link SignElement} which will be signed.
* @param singingKeyFingerprint {@link OpenPgpV4Fingerprint} of the key that is used for signing.
* @return {@link OpenPgpElement} which contains the signed, Base64 encoded {@link SignElement}.
* @throws Exception
* @throws MissingOpenPgpKeyPairException if we don't have the key pair for the
* {@link OpenPgpV4Fingerprint} available.
*/
OpenPgpElement sign(SignElement element) throws Exception;
OpenPgpElement sign(SignElement element, OpenPgpV4Fingerprint singingKeyFingerprint)
throws MissingOpenPgpKeyPairException;
/**
* Verify the signature on an incoming {@link OpenPgpElement} which must contain a {@link SignElement}.
*
* <br>
* Note: DO NOT use this method in the context of instant messaging, as XEP-0374 forbids that.
*
* @see <a href="https://xmpp.org/extensions/xep-0373.html#exchange">XEP-0373 §3.1</a>
* @see <a href="https://xmpp.org/extensions/xep-0374.html#openpgp-secured-im">XEP-0374 §2.1</a>
* @param element incoming {@link OpenPgpElement} which must contain a signed {@link SignElement}.
* @param sender {@link BareJid} of the sender which also signed the message.
* @param singingKeyFingerprints {@link Set} of the senders key {@link OpenPgpV4Fingerprint}s.
* It is required that one of those keys was used to sign
* the message.
* @return {@link OpenPgpMessage} which contains the decoded {@link SignElement}.
* @throws Exception
* @throws MissingOpenPgpPublicKeyException if we don't have the signers public key which signed
* the message available.
*/
OpenPgpMessage verify(OpenPgpElement element, BareJid sender) throws Exception;
OpenPgpMessage verify(OpenPgpElement element, Set<OpenPgpV4Fingerprint> singingKeyFingerprints)
throws MissingOpenPgpPublicKeyException;
/**
* Encrypt a {@link CryptElement} and pack it inside a {@link OpenPgpElement}.
* The resulting {@link OpenPgpElement} contains the encrypted and Base64 encoded {@link CryptElement}
* which can be decrypted by all recipients, as well as by ourselves.
*
* <br>
* Note: DO NOT use this method in the context of instant messaging, as XEP-0374 forbids that.
*
* @see <a href="https://xmpp.org/extensions/xep-0374.html#openpgp-secured-im">XEP-0374 §2.1</a>
* @param element plaintext {@link CryptElement} which will be encrypted.
* @param recipients {@link Set} of {@link BareJid} of recipients, which will be able to decrypt the message.
* @param encryptionKeyFingerprints {@link Set} of {@link OpenPgpV4Fingerprint}s of the keys which
* are used for encryption.
* @return {@link OpenPgpElement} which contains the encrypted, Base64 encoded {@link CryptElement}.
* @throws Exception
* @throws MissingOpenPgpPublicKeyException if any of the OpenPGP public keys whose
* {@link OpenPgpV4Fingerprint} is listed in {@code encryptionKeys}
* is not available.
*/
OpenPgpElement encrypt(CryptElement element, Set<BareJid> recipients) throws Exception;
OpenPgpElement encrypt(CryptElement element, Set<OpenPgpV4Fingerprint> encryptionKeyFingerprints)
throws MissingOpenPgpPublicKeyException;
/**
* Decrypt an incoming {@link OpenPgpElement} which must contain a {@link CryptElement}.
* The resulting {@link OpenPgpMessage} will contain the decrypted {@link CryptElement}.
*
* <br>
* Note: DO NOT use this method in the context of instant messaging, as XEP-0374 forbids that.
*
* @see <a href="https://xmpp.org/extensions/xep-0374.html#openpgp-secured-im">XEP-0374 §2.1</a>
* @param element {@link OpenPgpElement} which contains the encrypted {@link CryptElement}.
* @return {@link OpenPgpMessage} which contains the decrypted {@link CryptElement}.
* @throws Exception
* @throws MissingOpenPgpKeyPairException if we don't have an OpenPGP key pair available that to decrypt
* the message.
*/
OpenPgpMessage decrypt(OpenPgpElement element) throws Exception;
OpenPgpMessage decrypt(OpenPgpElement element)
throws MissingOpenPgpKeyPairException;
/**
* Create a {@link PubkeyElement} which contains our exported OpenPGP public key.
* The element can for example be published.
* Create a fresh OpenPGP key pair with the {@link BareJid} of the user prefixed by "xmpp:" as user-id
* (example: {@code "xmpp:juliet@capulet.lit"}).
* Store the key pair in persistent storage and return the public keys {@link OpenPgpV4Fingerprint}.
*
* @return {@link PubkeyElement} containing our public key.
* @throws CorruptedOpenPgpKeyException if our public key can for some reason not be serialized.
* @throws NoSuchAlgorithmException if a Hash algorithm is not available
* @throws NoSuchProviderException id no suitable cryptographic provider (for example BouncyCastleProvider)
* is registered.
*/
PubkeyElement createPubkeyElement() throws CorruptedOpenPgpKeyException;
/**
* Process an incoming {@link PubkeyElement} of a contact or ourselves.
* That typically includes importing/updating the key.
*
* @param element {@link PubkeyElement} which presumably contains the public key of the {@code owner}.
* @param owner owner of the OpenPGP public key contained in the {@link PubkeyElement}.
* @throws CorruptedOpenPgpKeyException if the key found in the {@link PubkeyElement}
* can not be deserialized or imported.
*/
void processPubkeyElement(PubkeyElement element, BareJid owner) throws CorruptedOpenPgpKeyException;
/**
* Process an incoming update to the OpenPGP metadata node.
* That typically includes fetching announced keys of which we don't have a local copy yet,
* as well as marking keys which are missing from the list as inactive.
*
* @param listElement {@link PublicKeysListElement} which contains a list of the keys of {@code owner}.
* @param owner {@link BareJid} of the owner of the announced public keys.
* @throws Exception
*/
void processPublicKeysListElement(PublicKeysListElement listElement, BareJid owner) throws Exception;
/**
* Return the OpenPGP v4-fingerprint of our key in hexadecimal upper case.
*
* @return fingerprint
* @throws CorruptedOpenPgpKeyException if for some reason the fingerprint cannot be derived from the key pair.
*/
String getFingerprint() throws CorruptedOpenPgpKeyException;
SecretkeyElement createSecretkeyElement(String password) throws CorruptedOpenPgpKeyException;
void restoreSecretKeyElement(SecretkeyElement secretkeyElement, String password) throws CorruptedOpenPgpKeyException;
void createAndUseKey() throws CorruptedOpenPgpKeyException, NoSuchAlgorithmException;
OpenPgpV4Fingerprint createOpenPgpKeyPair()
throws NoSuchAlgorithmException, NoSuchProviderException;
}

View file

@ -0,0 +1,124 @@
package org.jivesoftware.smackx.ox;
import java.util.Set;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smackx.ox.callback.SecretKeyRestoreSelectionCallback;
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.SigncryptElement;
import org.jivesoftware.smackx.ox.exception.CorruptedOpenPgpKeyException;
import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException;
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException;
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException;
import org.jxmpp.jid.BareJid;
public interface OpenPgpStore {
/**
* Return the {@link OpenPgpV4Fingerprint} of the primary OpenPGP key pair.
* If multiple key pairs are available, only the primary key pair is used for signing.
* <br>
* Note: This method returns {@code null} if no key pair is available.
*
* @return fingerprint of the primary OpenPGP key pair.
*/
OpenPgpV4Fingerprint primaryOpenPgpKeyPairFingerprint();
/**
* Return a {@link Set} containing the {@link OpenPgpV4Fingerprint} of all available OpenPGP key pairs.
*
* @return set of fingerprints of available OpenPGP key pairs.
*/
Set<OpenPgpV4Fingerprint> availableOpenPgpKeyPairFingerprints();
/**
* Return a {@link Set} containing the {@link OpenPgpV4Fingerprint}s of all currently announced OpenPGP
* public keys of a contact.
* <br>
* Note: Those are the keys announced in the latest received metadata update.
* This returns a {@link Set} which might be different from the result of
* {@link #availableOpenPgpKeysFingerprints(BareJid)}.
* Messages should be encrypted to the intersection of both sets.
*
* @param contact contact.
* @return list of contacts last announced public keys.
*/
Set<OpenPgpV4Fingerprint> announcedOpenPgpKeyFingerprints(BareJid contact);
/**
* Return a {@link Set} containing the {@link OpenPgpV4Fingerprint}s of all OpenPGP public keys of a
* contact, which we have locally available.
* <br>
* Note: This returns a {@link Set} that might be different from the result of
* {@link #availableOpenPgpKeysFingerprints(BareJid)}.
* Messages should be encrypted to the intersection of both sets.
*
* @param contact contact.
* @return list of contacts locally available public keys.
*/
Set<OpenPgpV4Fingerprint> availableOpenPgpKeysFingerprints(BareJid contact);
/**
* Store incoming update to the OpenPGP metadata node in persistent storage.
*
* @param connection authenticated {@link XMPPConnection} of the user.
* @param listElement {@link PublicKeysListElement} which contains a list of the keys of {@code owner}.
* @param owner {@link BareJid} of the owner of the announced public keys.
* @throws CorruptedOpenPgpKeyException
* @throws InterruptedException
* @throws SmackException.NotConnectedException
* @throws SmackException.NoResponseException
*/
void storePublicKeysList(XMPPConnection connection, PublicKeysListElement listElement, BareJid owner);
/**
* Create a {@link PubkeyElement} which contains our exported OpenPGP public key.
* The element can for example be published.
*
* @return {@link PubkeyElement} containing our public key.
* @throws CorruptedOpenPgpKeyException if our public key can for some reason not be serialized.
*/
PubkeyElement createPubkeyElement(OpenPgpV4Fingerprint fingerprint)
throws MissingOpenPgpPublicKeyException, CorruptedOpenPgpKeyException;
/**
* Process an incoming {@link PubkeyElement} of a contact or ourselves.
* That typically includes importing/updating the key.
*
* @param owner owner of the OpenPGP public key contained in the {@link PubkeyElement}.
* @param fingerprint {@link OpenPgpV4Fingerprint} of the key.
* @param element {@link PubkeyElement} which presumably contains the public key of the {@code owner}.
* @throws CorruptedOpenPgpKeyException if the key found in the {@link PubkeyElement}
* can not be deserialized or imported.
*/
void storePublicKey(BareJid owner, OpenPgpV4Fingerprint fingerprint, PubkeyElement element)
throws CorruptedOpenPgpKeyException;
/**
* Create an encrypted backup of our secret keys.
*
* @param fingerprints {@link Set} of IDs of the keys that will be included in the backup.
* @param password password that is used to symmetrically encrypt the backup.
* @return {@link SigncryptElement}.
* @throws MissingOpenPgpKeyPairException if we don't have an OpenPGP key available.
* @throws CorruptedOpenPgpKeyException if for some reason the key pair cannot be serialized.
*/
SecretkeyElement createSecretkeyElement(Set<OpenPgpV4Fingerprint> fingerprints, String password)
throws MissingOpenPgpKeyPairException, CorruptedOpenPgpKeyException;
/**
* Decrypt a secret key backup and restore the key from it.
*
* @param secretkeyElement {@link SecretkeyElement} containing the backup.
* @param password password to decrypt the backup.
* @param callback {@link SecretKeyRestoreSelectionCallback} to let the user decide which key to restore.
* @throws CorruptedOpenPgpKeyException if the selected key is corrupted and cannot be restored.
* @throws InvalidBackupCodeException if the user provided backup code is invalid.
*/
void restoreSecretKeyBackup(SecretkeyElement secretkeyElement, String password, SecretKeyRestoreSelectionCallback callback)
throws CorruptedOpenPgpKeyException, InvalidBackupCodeException;
}

View file

@ -0,0 +1,114 @@
/**
*
* Copyright 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 java.nio.charset.Charset;
import org.jivesoftware.smack.util.Objects;
/**
* This class represents an hex encoded, uppercase OpenPGP v4 fingerprint.
*/
public class OpenPgpV4Fingerprint implements CharSequence, Comparable<OpenPgpV4Fingerprint> {
private final String fingerprint;
/**
* Create an {@link OpenPgpV4Fingerprint}.
* @see <a href="https://xmpp.org/extensions/xep-0373.html#annoucning-pubkey">
* XEP-0373 §4.1: The OpenPGP Public-Key Data Node about how to obtain the fingerprint</a>
* @param fingerprint hexadecimal representation of the fingerprint.
*/
public OpenPgpV4Fingerprint(String fingerprint) {
String fp = Objects.requireNonNull(fingerprint)
.trim()
.toUpperCase();
if (!isValid(fp)) {
throw new IllegalArgumentException("Fingerprint " + fingerprint +
" does not appear to be a valid OpenPGP v4 fingerprint.");
}
this.fingerprint = fp;
}
public OpenPgpV4Fingerprint(byte[] bytes) {
this(new String(bytes, Charset.forName("UTF-8")));
}
/**
* Check, whether the fingerprint consists of 40 valid hexadecimal characters.
* @param fp fingerprint to check.
* @return true if fingerprint is valid.
*/
private boolean isValid(String fp) {
return fp.matches("[0-9A-F]{40}");
}
/**
* Return the key id of the OpenPGP public key this {@link OpenPgpV4Fingerprint} belongs to.
* This method uses {@link Util#keyIdFromFingerprint(OpenPgpV4Fingerprint)}.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-12.2">
* RFC-4880 §12.2: Key IDs and Fingerprints</a>
* @return key id
*/
public long getKeyId() {
return Util.keyIdFromFingerprint(this);
}
@Override
public boolean equals(Object other) {
if (other == null) {
return false;
}
if (!(other instanceof CharSequence)) {
return false;
}
return this.toString().equals(other.toString());
}
@Override
public int hashCode() {
return fingerprint.hashCode();
}
@Override
public int length() {
return fingerprint.length();
}
@Override
public char charAt(int i) {
return fingerprint.charAt(i);
}
@Override
public CharSequence subSequence(int i, int i1) {
return fingerprint.subSequence(i, i1);
}
@Override
public String toString() {
return fingerprint;
}
@Override
public int compareTo(OpenPgpV4Fingerprint openPgpV4Fingerprint) {
return fingerprint.compareTo(openPgpV4Fingerprint.fingerprint);
}
}

View file

@ -0,0 +1,266 @@
/**
*
* Copyright 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 java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
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.element.SecretkeyElement;
import org.jivesoftware.smackx.ox.exception.CorruptedOpenPgpKeyException;
import org.jivesoftware.smackx.pubsub.AccessModel;
import org.jivesoftware.smackx.pubsub.ConfigureForm;
import org.jivesoftware.smackx.pubsub.Item;
import org.jivesoftware.smackx.pubsub.LeafNode;
import org.jivesoftware.smackx.pubsub.PayloadItem;
import org.jivesoftware.smackx.pubsub.PubSubException;
import org.jivesoftware.smackx.pubsub.PubSubManager;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.jxmpp.jid.BareJid;
public class PubSubDelegate {
private static final Logger LOGGER = Logger.getLogger(PubSubDelegate.class.getName());
/**
* Name of the OX metadata node.
*
* @see <a href="https://xmpp.org/extensions/xep-0373.html#announcing-pubkey-list">XEP-0373 §4.2</a>
*/
public static final String PEP_NODE_PUBLIC_KEYS = "urn:xmpp:openpgp:0:public-keys";
/**
* Name of the OX secret key node.
*/
public static final String PEP_NODE_SECRET_KEY = "urn:xmpp:openpgp:0:secret-key";
/**
* Feature to be announced using the {@link ServiceDiscoveryManager} to subscribe to the OX metadata node.
*
* @see <a href="https://xmpp.org/extensions/xep-0373.html#pubsub-notifications">XEP-0373 §4.4</a>
*/
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(OpenPgpV4Fingerprint id) {
return PEP_NODE_PUBLIC_KEYS + ":" + id;
}
public static void changeAccessModelIfNecessary(LeafNode node, AccessModel accessModel)
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException {
ConfigureForm current = node.getNodeConfiguration();
if (current.getAccessModel() != accessModel) {
ConfigureForm updateConfig = new ConfigureForm(DataForm.Type.submit);
updateConfig.setAccessModel(accessModel);
node.sendConfigurationForm(updateConfig);
}
}
/**
* 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 <a href="https://xmpp.org/extensions/xep-0373.html#annoucning-pubkey">XEP-0373 §4.1</a>
*
* @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 static void publishPublicKey(XMPPConnection connection, PubkeyElement pubkeyElement, OpenPgpV4Fingerprint fingerprint)
throws CorruptedOpenPgpKeyException, InterruptedException, PubSubException.NotALeafNodeException,
XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException {
String keyNodeName = PEP_NODE_PUBLIC_KEY(fingerprint);
PubSubManager pm = PubSubManager.getInstance(connection, connection.getUser().asBareJid());
// Check if key available at data node
// If not, publish key to data node
LeafNode keyNode = pm.getOrCreateLeafNode(keyNodeName);
List<Item> items = keyNode.getItems(1);
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.");
}
// Fetch IDs from metadata node
LeafNode metadataNode = pm.getOrCreateLeafNode(PEP_NODE_PUBLIC_KEYS);
List<PayloadItem<PublicKeysListElement>> metadataItems = metadataNode.getItems(1);
PublicKeysListElement.Builder builder = PublicKeysListElement.builder();
if (!metadataItems.isEmpty() && metadataItems.get(0).getPayload() != null) {
// Add old entries back to list.
PublicKeysListElement publishedList = metadataItems.get(0).getPayload();
for (PublicKeysListElement.PubkeyMetadataElement meta : publishedList.getMetadata().values()) {
builder.addMetadata(meta);
}
}
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 static PublicKeysListElement fetchPubkeysList(XMPPConnection connection)
throws InterruptedException, PubSubException.NotALeafNodeException, SmackException.NoResponseException,
SmackException.NotConnectedException, XMPPException.XMPPErrorException,
PubSubException.NotAPubSubNodeException {
return fetchPubkeysList(connection, connection.getUser().asBareJid());
}
/**
* 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 static PublicKeysListElement fetchPubkeysList(XMPPConnection connection, BareJid contact)
throws InterruptedException, PubSubException.NotALeafNodeException, SmackException.NoResponseException,
SmackException.NotConnectedException, XMPPException.XMPPErrorException,
PubSubException.NotAPubSubNodeException {
PubSubManager pm = PubSubManager.getInstance(connection, contact);
LeafNode node = pm.getLeafNode(PEP_NODE_PUBLIC_KEYS);
List<PayloadItem<PublicKeysListElement>> list = node.getItems(1);
if (list.isEmpty()) {
return null;
}
return list.get(0).getPayload();
}
/**
* Delete our metadata node.
*
* @throws XMPPException.XMPPErrorException
* @throws SmackException.NotConnectedException
* @throws InterruptedException
* @throws SmackException.NoResponseException
*/
public static void deletePubkeysListNode(XMPPConnection connection)
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 <a href="https://xmpp.org/extensions/xep-0373.html#discover-pubkey">XEP-0373 §4.3</a>
*
* @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 static PubkeyElement fetchPubkey(XMPPConnection connection, BareJid contact, OpenPgpV4Fingerprint v4_fingerprint)
throws InterruptedException, PubSubException.NotALeafNodeException, SmackException.NoResponseException,
SmackException.NotConnectedException, XMPPException.XMPPErrorException,
PubSubException.NotAPubSubNodeException {
PubSubManager pm = PubSubManager.getInstance(connection, contact);
LeafNode node = pm.getLeafNode(PEP_NODE_PUBLIC_KEY(v4_fingerprint));
List<PayloadItem<PubkeyElement>> list = node.getItems(1);
if (list.isEmpty()) {
return null;
}
return list.get(0).getPayload();
}
/**
* TODO: Implement and document.
*/
public static void depositSecretKey(XMPPConnection connection, SecretkeyElement element)
throws InterruptedException, PubSubException.NotALeafNodeException,
XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException {
PubSubManager pm = PubSubManager.getInstance(connection);
LeafNode secretKeyNode = pm.getOrCreateLeafNode(PEP_NODE_SECRET_KEY);
PubSubDelegate.changeAccessModelIfNecessary(secretKeyNode, AccessModel.whitelist);
secretKeyNode.publish(new PayloadItem<>(element));
}
public static SecretkeyElement fetchSecretKey(XMPPConnection connection)
throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, SmackException.NoResponseException {
PubSubManager pm = PubSubManager.getInstance(connection);
LeafNode secretKeyNode = pm.getOrCreateLeafNode(PEP_NODE_SECRET_KEY);
List<PayloadItem<SecretkeyElement>> list = secretKeyNode.getItems(1);
if (list.size() == 0) {
LOGGER.log(Level.INFO, "No secret key published!");
return null;
}
SecretkeyElement secretkeyElement = list.get(0).getPayload();
return secretkeyElement;
}
public static void deleteSecretKeyNode(XMPPConnection connection)
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException {
PubSubManager pm = PubSubManager.getInstance(connection);
pm.deleteNode(PEP_NODE_SECRET_KEY);
}
}

View file

@ -1,48 +0,0 @@
/**
*
* Copyright 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 org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.pubsub.AccessModel;
import org.jivesoftware.smackx.pubsub.ConfigureForm;
import org.jivesoftware.smackx.pubsub.LeafNode;
import org.jivesoftware.smackx.xdata.packet.DataForm;
public class PubSubHelper {
public static void whitelist(LeafNode node)
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException {
ConfigureForm old = node.getNodeConfiguration();
if (old.getAccessModel() != AccessModel.whitelist) {
ConfigureForm _new = new ConfigureForm(DataForm.Type.submit);
_new.setAccessModel(AccessModel.whitelist);
node.sendConfigurationForm(_new);
}
}
public static void open(LeafNode node)
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
SmackException.NoResponseException {
ConfigureForm config = node.getNodeConfiguration();
if (config.getAccessModel() != AccessModel.open) {
config.setAccessModel(AccessModel.open);
node.sendConfigurationForm(config);
}
}
}

View file

@ -0,0 +1,24 @@
package org.jivesoftware.smackx.ox;
import java.nio.ByteBuffer;
import java.util.Arrays;
import javax.xml.bind.DatatypeConverter;
public class Util {
/**
* Calculate the key id of the OpenPGP key, the given {@link OpenPgpV4Fingerprint} belongs to.
*
* @see <a href="https://tools.ietf.org/html/rfc4880#section-12.2"> RFC-4880 §12.2</a>
* @param fingerprint {@link OpenPgpV4Fingerprint}.
* @return key id
*/
public static long keyIdFromFingerprint(OpenPgpV4Fingerprint fingerprint) {
byte[] bytes = DatatypeConverter.parseHexBinary(fingerprint.toString());
byte[] lower8Bytes = Arrays.copyOfRange(bytes, 12, 20);
ByteBuffer byteBuffer = ByteBuffer.allocate(8);
byteBuffer.put(lower8Bytes);
byteBuffer.flip();
return byteBuffer.getLong();
}
}

View file

@ -0,0 +1,21 @@
package org.jivesoftware.smackx.ox.callback;
import java.util.Set;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
/**
* Callback to allow the user to decide, which locally available secret keys they want to include in a backup.
*/
public interface SecretKeyBackupSelectionCallback {
/**
* Let the user decide, which secret keys they want to backup.
*
* @param availableSecretKeys {@link Set} of {@link OpenPgpV4Fingerprint}s of locally available
* OpenPGP secret keys.
* @return {@link Set} which contains the {@link OpenPgpV4Fingerprint}s the user decided to include
* in the backup.
*/
Set<OpenPgpV4Fingerprint> selectKeysToBackup(Set<OpenPgpV4Fingerprint> availableSecretKeys);
}

View file

@ -0,0 +1,36 @@
/**
*
* Copyright 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.callback;
import java.util.Set;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
/**
* Callback to let the user decide which key from a backup they want to restore.
*/
public interface SecretKeyRestoreSelectionCallback {
/**
* Let the user choose, which SecretKey they want to restore as the new primary OpenPGP signing key.
* @param availableSecretKeys {@link Set} of {@link OpenPgpV4Fingerprint}s of the keys which are contained
* in the backup.
* @return {@link OpenPgpV4Fingerprint} of the key the user wants to restore as the new primary
* signing key.
*/
OpenPgpV4Fingerprint selectSecretKeyToRestore(Set<OpenPgpV4Fingerprint> availableSecretKeys);
}

View file

@ -25,15 +25,16 @@ import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.NamedElement;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
public final class PublicKeysListElement implements ExtensionElement {
public static final String NAMESPACE = OpenPgpElement.NAMESPACE;
public static final String ELEMENT = "public-keys-list";
private final Map<String, PubkeyMetadataElement> metadata;
private final Map<OpenPgpV4Fingerprint, PubkeyMetadataElement> metadata;
private PublicKeysListElement(TreeMap<String, PubkeyMetadataElement> metadata) {
private PublicKeysListElement(TreeMap<OpenPgpV4Fingerprint, PubkeyMetadataElement> metadata) {
this.metadata = Collections.unmodifiableMap(metadata);
}
@ -41,7 +42,7 @@ public final class PublicKeysListElement implements ExtensionElement {
return new Builder();
}
public TreeMap<String, PubkeyMetadataElement> getMetadata() {
public TreeMap<OpenPgpV4Fingerprint, PubkeyMetadataElement> getMetadata() {
return new TreeMap<>(metadata);
}
@ -67,7 +68,7 @@ public final class PublicKeysListElement implements ExtensionElement {
public static final class Builder {
private final TreeMap<String, PubkeyMetadataElement> metadata = new TreeMap<>();
private final TreeMap<OpenPgpV4Fingerprint, PubkeyMetadataElement> metadata = new TreeMap<>();
private Builder() {
// Empty
@ -89,10 +90,10 @@ public final class PublicKeysListElement implements ExtensionElement {
public static final String ATTR_V4_FINGERPRINT = "v4-fingerprint";
public static final String ATTR_DATE = "date";
private final String v4_fingerprint;
private final OpenPgpV4Fingerprint v4_fingerprint;
private final Date date;
public PubkeyMetadataElement(String v4_fingerprint, Date date) {
public PubkeyMetadataElement(OpenPgpV4Fingerprint v4_fingerprint, Date date) {
this.v4_fingerprint = Objects.requireNonNull(v4_fingerprint);
this.date = Objects.requireNonNull(date);
@ -101,7 +102,7 @@ public final class PublicKeysListElement implements ExtensionElement {
}
}
public String getV4Fingerprint() {
public OpenPgpV4Fingerprint getV4Fingerprint() {
return v4_fingerprint;
}

View file

@ -0,0 +1,25 @@
/**
*
* Copyright 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.exception;
/**
* Exception that gets thrown if the backup code entered by the user is invalid.
*/
public class InvalidBackupCodeException extends Exception {
private static final long serialVersionUID = 1L;
}

View file

@ -0,0 +1,48 @@
/**
*
* Copyright 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.exception;
import org.jxmpp.jid.BareJid;
/**
* Exception that gets thrown whenever an operation is missing an OpenPGP key pair.
*/
public class MissingOpenPgpKeyPairException extends Exception {
private static final long serialVersionUID = 1L;
private final BareJid owner;
/**
* Create a new {@link MissingOpenPgpKeyPairException}.
*
* @param owner owner of the missing key pair.
*/
public MissingOpenPgpKeyPairException(BareJid owner) {
super("Missing OpenPGP key pair for user " + owner);
this.owner = owner;
}
/**
* Return the owner of the missing OpenPGP key pair.
*
* @return owner
*/
public BareJid getOwner() {
return owner;
}
}

View file

@ -0,0 +1,62 @@
/**
*
* Copyright 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.exception;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.jxmpp.jid.BareJid;
/**
* Exception that gets thrown when an operation is missing an OpenPGP public key.
*/
public class MissingOpenPgpPublicKeyException extends Exception {
private static final long serialVersionUID = 1L;
private final BareJid user;
private final OpenPgpV4Fingerprint fingerprint;
/**
* Create a new {@link MissingOpenPgpPublicKeyException}.
*
* @param owner {@link BareJid} of the keys owner.
* @param fingerprint {@link OpenPgpV4Fingerprint} of the missing key.
*/
public MissingOpenPgpPublicKeyException(BareJid owner, OpenPgpV4Fingerprint fingerprint) {
super("Missing public key " + fingerprint.toString() + " for user " + owner + ".");
this.user = owner;
this.fingerprint = fingerprint;
}
/**
* Return the {@link BareJid} of the owner of the missing key.
*
* @return owner of missing key.
*/
public BareJid getUser() {
return user;
}
/**
* Return the fingerprint of the missing key.
*
* @return {@link OpenPgpV4Fingerprint} of the missing key.
*/
public OpenPgpV4Fingerprint getFingerprint() {
return fingerprint;
}
}

View file

@ -20,6 +20,9 @@ import org.jivesoftware.smackx.ox.element.CryptElement;
import org.xmlpull.v1.XmlPullParser;
/**
* {@link org.jivesoftware.smack.provider.ExtensionElementProvider} implementation for the {@link CryptElement}.
*/
public class CryptElementProvider extends OpenPgpContentElementProvider<CryptElement> {
public static final CryptElementProvider TEST_INSTANCE = new CryptElementProvider();

View file

@ -44,6 +44,11 @@ import org.jxmpp.util.XmppDateTime;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
/**
* Abstract {@link ExtensionElementProvider} implementation for the also abstract
* {@link OpenPgpContentElement}.
* @param <O> Specialized subclass of {@link OpenPgpContentElement}.
*/
public abstract class OpenPgpContentElementProvider<O extends OpenPgpContentElement> extends ExtensionElementProvider<O> {
private static final Logger LOGGER = Logger.getLogger(OpenPgpContentElementProvider.class.getName());

View file

@ -21,6 +21,9 @@ import org.jivesoftware.smackx.ox.element.OpenPgpElement;
import org.xmlpull.v1.XmlPullParser;
/**
* {@link ExtensionElementProvider} implementation for the {@link OpenPgpElement}.
*/
public class OpenPgpElementProvider extends ExtensionElementProvider<OpenPgpElement> {
public static final OpenPgpElementProvider TEST_INSTANCE = new OpenPgpElementProvider();

View file

@ -27,6 +27,9 @@ import org.jivesoftware.smackx.ox.element.PubkeyElement;
import org.jxmpp.util.XmppDateTime;
import org.xmlpull.v1.XmlPullParser;
/**
* {@link ExtensionElementProvider} implementation for the {@link PubkeyElement}.
*/
public class PubkeyElementProvider extends ExtensionElementProvider<PubkeyElement> {
public static final PubkeyElementProvider TEST_INSTANCE = new PubkeyElementProvider();

View file

@ -22,6 +22,7 @@ import static org.xmlpull.v1.XmlPullParser.START_TAG;
import java.util.Date;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint;
import org.jivesoftware.smackx.ox.element.PublicKeysListElement;
import org.jxmpp.util.XmppDateTime;
@ -49,8 +50,9 @@ public final class PublicKeysListElementProvider extends ExtensionElementProvide
PublicKeysListElement.PubkeyMetadataElement.ATTR_V4_FINGERPRINT);
String dt = parser.getAttributeValue(null,
PublicKeysListElement.PubkeyMetadataElement.ATTR_DATE);
OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(finger);
Date date = XmppDateTime.parseXEP0082Date(dt);
builder.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(finger, date));
builder.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(fingerprint, date));
}
break;

View file

@ -23,6 +23,9 @@ import org.jivesoftware.smackx.ox.element.SecretkeyElement;
import org.xmlpull.v1.XmlPullParser;
/**
* {@link ExtensionElementProvider} implementation for the {@link SecretkeyElement}.
*/
public class SecretkeyElementProvider extends ExtensionElementProvider<SecretkeyElement> {
public static final SecretkeyElementProvider TEST_INSTANCE = new SecretkeyElementProvider();

View file

@ -23,6 +23,9 @@ import org.jivesoftware.smackx.ox.element.SignElement;
import org.xmlpull.v1.XmlPullParser;
/**
* {@link org.jivesoftware.smack.provider.ExtensionElementProvider} implementation for the {@link SignElement}.
*/
public class SignElementProvider extends OpenPgpContentElementProvider<SignElement> {
private static final Logger LOGGER = Logger.getLogger(SigncryptElementProvider.class.getName());

View file

@ -20,6 +20,9 @@ import org.jivesoftware.smackx.ox.element.SigncryptElement;
import org.xmlpull.v1.XmlPullParser;
/**
* {@link org.jivesoftware.smack.provider.ExtensionElementProvider} implementation for the {@link SigncryptElement}.
*/
public class SigncryptElementProvider extends OpenPgpContentElementProvider<SigncryptElement> {
public static final SigncryptElementProvider TEST_INSTANCE = new SigncryptElementProvider();

View file

@ -0,0 +1,58 @@
/**
*
* Copyright 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 junit.framework.TestCase.assertEquals;
import org.jivesoftware.smack.test.util.SmackTestSuite;
import org.junit.Test;
public class OpenPgpV4FingerprintTest extends SmackTestSuite {
@Test(expected = IllegalArgumentException.class)
public void fpTooShort() {
String fp = "484f57414c495645"; // Asking Mark
new OpenPgpV4Fingerprint(fp);
}
@Test(expected = IllegalArgumentException.class)
public void invalidHexTest() {
String fp = "UNFORTUNATELYTHISISNOVALIDHEXADECIMALDOH";
new OpenPgpV4Fingerprint(fp);
}
@Test
public void validFingerprintTest() {
String fp = "4A4F48414E4E53454E2049532041204E45524421";
OpenPgpV4Fingerprint finger = new OpenPgpV4Fingerprint(fp);
assertEquals(fp, finger.toString());
}
@Test
public void convertsToUpperCaseTest() {
String fp = "444f4e5420552048415645204120484f4242593f";
OpenPgpV4Fingerprint finger = new OpenPgpV4Fingerprint(fp);
assertEquals("444F4E5420552048415645204120484F4242593F", finger.toString());
}
@Test
public void equalsOtherFingerprintTest() {
OpenPgpV4Fingerprint finger = new OpenPgpV4Fingerprint("5448452043414b452049532041204c4945212121");
assertEquals(finger, new OpenPgpV4Fingerprint("5448452043414B452049532041204C4945212121"));
}
}

View file

@ -51,10 +51,10 @@ public class PublicKeysListElementTest extends SmackTestSuite {
Date date2 = XmppDateTime.parseDate("1953-05-16T12:00:00.000+00:00");
PublicKeysListElement.PubkeyMetadataElement child1 =
new PublicKeysListElement.PubkeyMetadataElement(
"1357B01865B2503C18453D208CAC2A9678548E35", date1);
new OpenPgpV4Fingerprint("1357B01865B2503C18453D208CAC2A9678548E35"), date1);
PublicKeysListElement.PubkeyMetadataElement child2 =
new PublicKeysListElement.PubkeyMetadataElement(
"67819B343B2AB70DED9320872C6464AF2A8E4C02", date2);
new OpenPgpV4Fingerprint("67819B343B2AB70DED9320872C6464AF2A8E4C02"), date2);
PublicKeysListElement element = PublicKeysListElement.builder()
.addMetadata(child1)
@ -72,22 +72,16 @@ public class PublicKeysListElementTest extends SmackTestSuite {
@Test
public void listBuilderRefusesDuplicatesTest() {
PublicKeysListElement.Builder builder = PublicKeysListElement.builder();
String fp40 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN";
String fp40 = "49545320414c4c2041424f555420444120484558";
Date oneDate = new Date(12337883234L);
Date otherDate = new Date(8888348384L);
// Check if size of metadata is one after insert.
builder.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(fp40, oneDate));
builder.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(new OpenPgpV4Fingerprint(fp40), oneDate));
assertEquals(builder.build().getMetadata().size(), 1);
// Check if size is still one after inserting element with same fp.
builder.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(fp40, otherDate));
builder.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(new OpenPgpV4Fingerprint(fp40), otherDate));
assertEquals(builder.build().getMetadata().size(), 1);
}
@Test(expected = IllegalArgumentException.class)
public void metadataFingerprintLengthTest() {
PublicKeysListElement.PubkeyMetadataElement element =
new PublicKeysListElement.PubkeyMetadataElement("thisIsNotTheCorrectLength", new Date());
}
}