From ffc8cbbf79f8adf6a71547c0493b557d1070a6dd Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 1 Jun 2018 13:11:43 +0200 Subject: [PATCH] Several changes Add encrypted message listeners Automatically decrypt incoming messages Add convenient methods to get payload --- .../bouncycastle/FileBasedBcOpenPgpStore.java | 2 +- .../smackx/ox/OXInstantMessagingManager.java | 72 ++++++++++++++--- .../ox/element/OpenPgpContentElement.java | 79 +++++++++++++++++-- ...a => OpenPgpEncryptedMessageListener.java} | 2 +- .../ox/listener/OpenPgpMessageListener.java | 50 ------------ .../smackx/ox/OpenPgpElementTest.java | 6 +- 6 files changed, 142 insertions(+), 69 deletions(-) rename smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/listener/{OXEncryptedChatMessageListener.java => OpenPgpEncryptedMessageListener.java} (95%) delete mode 100644 smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/listener/OpenPgpMessageListener.java diff --git a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBcOpenPgpStore.java b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBcOpenPgpStore.java index 7a909ba85..372d94e5b 100644 --- a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBcOpenPgpStore.java +++ b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBcOpenPgpStore.java @@ -127,7 +127,7 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore { } } } catch (IOException | PGPException e) { - LOGGER.log(Level.SEVERE, "Error going through available key pair.", e); + LOGGER.log(Level.SEVERE, "Error going through available key pairs.", e); } return availableKeyPairs; } diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OXInstantMessagingManager.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OXInstantMessagingManager.java index 671da17aa..866963fd6 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OXInstantMessagingManager.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OXInstantMessagingManager.java @@ -16,8 +16,12 @@ */ package org.jivesoftware.smackx.ox; +import java.io.IOException; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.WeakHashMap; +import java.util.logging.Level; import java.util.logging.Logger; import org.jivesoftware.smack.Manager; @@ -26,11 +30,20 @@ import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.chat2.Chat; import org.jivesoftware.smack.chat2.ChatManager; +import org.jivesoftware.smack.chat2.IncomingChatMessageListener; +import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; +import org.jivesoftware.smackx.ox.element.OpenPgpContentElement; +import org.jivesoftware.smackx.ox.element.OpenPgpElement; +import org.jivesoftware.smackx.ox.element.SigncryptElement; +import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException; +import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException; import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException; +import org.jivesoftware.smackx.ox.listener.OpenPgpEncryptedMessageListener; import org.jxmpp.jid.BareJid; import org.jxmpp.jid.EntityBareJid; +import org.xmlpull.v1.XmlPullParserException; /** * Entry point of Smacks API for XEP-0374: OpenPGP for XMPP: Instant Messaging. @@ -48,10 +61,13 @@ public final class OXInstantMessagingManager extends Manager { private final OpenPgpManager openPgpManager; private final ChatManager chatManager; - private OXInstantMessagingManager(XMPPConnection connection) { + private final Set chatMessageListeners = new HashSet<>(); + + private OXInstantMessagingManager(final XMPPConnection connection) { super(connection); this.openPgpManager = OpenPgpManager.getInstanceFor(connection); this.chatManager = ChatManager.getInstanceFor(connection); + chatManager.addIncomingListener(incomingChatMessageListener); } public static OXInstantMessagingManager getInstanceFor(XMPPConnection connection) { @@ -91,15 +107,10 @@ public final class OXInstantMessagingManager extends Manager { * @throws XMPPException.XMPPErrorException * @throws SmackException.NotConnectedException * @throws SmackException.NoResponseException - * @throws SmackException.FeatureNotSupportedException if the contact does not announce support for XEP-0374. */ public OpenPgpEncryptedChat chatWith(EntityBareJid jid) throws SmackOpenPgpException, InterruptedException, XMPPException.XMPPErrorException, - SmackException.NotConnectedException, SmackException.NoResponseException, - SmackException.FeatureNotSupportedException { - if (!contactSupportsOxInstantMessaging(jid)) { - throw new SmackException.FeatureNotSupportedException(NAMESPACE_0, jid); - } + SmackException.NotConnectedException, SmackException.NoResponseException { OpenPgpFingerprints theirKeys = openPgpManager.determineContactsKeys(jid); OpenPgpFingerprints ourKeys = openPgpManager.determineContactsKeys(connection().getUser().asBareJid()); @@ -107,7 +118,50 @@ public final class OXInstantMessagingManager extends Manager { return new OpenPgpEncryptedChat(openPgpManager.getOpenPgpProvider(), chat, ourKeys, theirKeys); } - public void addOpenPgpEncryptedMessageListener() { - + public boolean addOpenPgpEncryptedMessageListener(OpenPgpEncryptedMessageListener listener) { + return chatMessageListeners.add(listener); } + + public boolean removeOpenPgpEncryptedMessageListener(OpenPgpEncryptedMessageListener listener) { + return chatMessageListeners.remove(listener); + } + + private final IncomingChatMessageListener incomingChatMessageListener = + new IncomingChatMessageListener() { + @Override + public void newIncomingMessage(EntityBareJid from, Message message, Chat chat) { + OpenPgpElement element = message.getExtension(OpenPgpElement.ELEMENT, OpenPgpElement.NAMESPACE); + if (element == null) { + return; + } + + OpenPgpProvider provider = openPgpManager.getOpenPgpProvider(); + + try { + OpenPgpEncryptedChat encryptedChat = chatWith(from); + OpenPgpMessage decrypted = provider.decryptAndVerify(element, provider.availableOpenPgpPublicKeysFingerprints(from.asBareJid())); + OpenPgpContentElement contentElement = decrypted.getOpenPgpContentElement(); + if (decrypted.getState() != OpenPgpMessage.State.signcrypt) { + LOGGER.log(Level.WARNING, "Decrypted content is not a signcrypt element. Ignore it."); + return; + } + + SigncryptElement signcryptElement = (SigncryptElement) contentElement; + for (OpenPgpEncryptedMessageListener l : chatMessageListeners) { + l.newIncomingEncryptedMessage(from, message, signcryptElement, encryptedChat); + } + } catch (SmackOpenPgpException e) { + LOGGER.log(Level.WARNING, "Could not start chat with " + from, e); + } catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NoResponseException e) { + LOGGER.log(Level.WARNING, "Something went wrong.", e); + } catch (MissingOpenPgpPublicKeyException e) { + LOGGER.log(Level.WARNING, "Could not verify message " + message.getStanzaId() + ": Missing senders public key " + e.getFingerprint().toString(), e); + } catch (MissingOpenPgpKeyPairException e) { + LOGGER.log(Level.WARNING, "Could not decrypt message " + message.getStanzaId() + ": Missing secret key", e); + } catch (XmlPullParserException | IOException e) { + LOGGER.log(Level.WARNING, "Could not parse decrypted content element", e); + } + + } + }; } diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/element/OpenPgpContentElement.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/element/OpenPgpContentElement.java index 261faae17..e48e29279 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/element/OpenPgpContentElement.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/element/OpenPgpContentElement.java @@ -16,6 +16,8 @@ */ package org.jivesoftware.smackx.ox.element; +import static org.jivesoftware.smack.util.StringUtils.requireNotNullOrEmpty; + import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.charset.Charset; @@ -24,10 +26,13 @@ import java.util.List; import java.util.Set; import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.util.MultiMap; +import org.jivesoftware.smack.util.PacketUtil; import org.jivesoftware.smack.util.XmlStringBuilder; import org.jxmpp.jid.Jid; import org.jxmpp.util.XmppDateTime; +import org.jxmpp.util.XmppStringUtils; /** * This class describes an OpenPGP content element. It defines the elements and fields that OpenPGP content elements @@ -43,14 +48,17 @@ public abstract class OpenPgpContentElement implements ExtensionElement { private final Set to; private final Date timestamp; - private final List payload; + private final MultiMap payload; private String timestampString; protected OpenPgpContentElement(Set to, Date timestamp, List payload) { this.to = to; this.timestamp = timestamp; - this.payload = payload; + this.payload = new MultiMap<>(); + for (ExtensionElement e : payload) { + this.payload.put(XmppStringUtils.generateKey(e.getElementName(), e.getNamespace()), e); + } } /** @@ -73,10 +81,71 @@ public abstract class OpenPgpContentElement implements ExtensionElement { * Return the payload of the message. * @return payload. */ - public final List getPayload() { - return payload; + public final List getExtensions() { + synchronized (payload) { + return payload.values(); + } } + /** + * Return a list of all extensions with the given element name and namespace. + *

+ * Changes to the returned set will update the stanza extensions, if the returned set is not the empty set. + *

+ * + * @param elementName the element name, must not be null. + * @param namespace the namespace of the element(s), must not be null. + * @return a set of all matching extensions. + * @since 4.1 + */ + public List getExtensions(String elementName, String namespace) { + requireNotNullOrEmpty(elementName, "elementName must not be null or empty"); + requireNotNullOrEmpty(namespace, "namespace must not be null or empty"); + String key = XmppStringUtils.generateKey(elementName, namespace); + return payload.getAll(key); + } + + /** + * Returns the first extension of this stanza that has the given namespace. + *

+ * When possible, use {@link #getExtension(String,String)} instead. + *

+ * + * @param namespace the namespace of the extension that is desired. + * @return the stanza extension with the given namespace. + */ + public ExtensionElement getExtension(String namespace) { + return PacketUtil.extensionElementFrom(getExtensions(), null, namespace); + } + + /** + * Returns the first extension that matches the specified element name and + * namespace, or null if it doesn't exist. If the provided elementName is null, + * only the namespace is matched. Extensions are + * are arbitrary XML elements in standard XMPP stanzas. + * + * @param elementName the XML element name of the extension. (May be null) + * @param namespace the XML element namespace of the extension. + * @param type of the ExtensionElement. + * @return the extension, or null if it doesn't exist. + */ + @SuppressWarnings("unchecked") + public PE getExtension(String elementName, String namespace) { + if (namespace == null) { + return null; + } + String key = XmppStringUtils.generateKey(elementName, namespace); + ExtensionElement packetExtension; + synchronized (payload) { + packetExtension = payload.getFirst(key); + } + if (packetExtension == null) { + return null; + } + return (PE) packetExtension; + } + + @Override public String getNamespace() { return OpenPgpElement.NAMESPACE; @@ -97,7 +166,7 @@ public abstract class OpenPgpContentElement implements ExtensionElement { xml.halfOpenElement(ELEM_TIME).attribute(ATTR_STAMP, timestampString).closeEmptyElement(); xml.openElement(ELEM_PAYLOAD); - for (ExtensionElement element : payload) { + for (ExtensionElement element : payload.values()) { xml.append(element.toXML(getNamespace())); } xml.closeElement(ELEM_PAYLOAD); diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/listener/OXEncryptedChatMessageListener.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/listener/OpenPgpEncryptedMessageListener.java similarity index 95% rename from smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/listener/OXEncryptedChatMessageListener.java rename to smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/listener/OpenPgpEncryptedMessageListener.java index c0ac29447..05384a798 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/listener/OXEncryptedChatMessageListener.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/listener/OpenPgpEncryptedMessageListener.java @@ -22,7 +22,7 @@ import org.jivesoftware.smackx.ox.element.SigncryptElement; import org.jxmpp.jid.EntityBareJid; -public interface OXEncryptedChatMessageListener { +public interface OpenPgpEncryptedMessageListener { void newIncomingEncryptedMessage(EntityBareJid from, Message originalMessage, diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/listener/OpenPgpMessageListener.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/listener/OpenPgpMessageListener.java deleted file mode 100644 index 5aabdf7fe..000000000 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/listener/OpenPgpMessageListener.java +++ /dev/null @@ -1,50 +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.listener; - -import org.jivesoftware.smackx.ox.element.CryptElement; -import org.jivesoftware.smackx.ox.element.SignElement; -import org.jivesoftware.smackx.ox.element.SigncryptElement; - -import org.jxmpp.jid.BareJid; - -interface OpenPgpMessageListener { - - /** - * This method gets called whenever we received and successfully decrypted/verified an encrypted, signed message. - * - * @param from sender/signer of the message. - * @param signcryptElement decrypted and verified {@link SigncryptElement}. - */ - void signcryptElementReceived(BareJid from, SigncryptElement signcryptElement); - - /** - * This method gets called whenever we received and successfully verified a signed message. - * - * @param from sender/signer of the message. - * @param signElement verified {@link SignElement}. - */ - void signElementReceived(BareJid from, SignElement signElement); - - /** - * This method gets called whenever we received and successfully decrypted an encrypted message. - * - * @param from sender of the message. - * @param cryptElement decrypted {@link CryptElement}. - */ - void cryptElementReceived(BareJid from, CryptElement cryptElement); -} diff --git a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/OpenPgpElementTest.java b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/OpenPgpElementTest.java index 212a24110..ba89512f9 100644 --- a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/OpenPgpElementTest.java +++ b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/OpenPgpElementTest.java @@ -108,7 +108,7 @@ public class OpenPgpElementTest extends SmackTestSuite { assertEquals(element.getTimestamp(), parsed.getTimestamp()); assertEquals(element.getTo(), parsed.getTo()); - assertEquals(element.getPayload(), parsed.getPayload()); + assertEquals(element.getExtensions(), parsed.getExtensions()); } @Test @@ -138,7 +138,7 @@ public class OpenPgpElementTest extends SmackTestSuite { assertEquals(element.getTimestamp(), parsed.getTimestamp()); assertEquals(element.getTo(), parsed.getTo()); - assertEquals(element.getPayload(), parsed.getPayload()); + assertEquals(element.getExtensions(), parsed.getExtensions()); } @Test @@ -168,7 +168,7 @@ public class OpenPgpElementTest extends SmackTestSuite { assertEquals(element.getTimestamp(), parsed.getTimestamp()); assertEquals(element.getTo(), parsed.getTo()); - assertEquals(element.getPayload(), parsed.getPayload()); + assertEquals(element.getExtensions(), parsed.getExtensions()); }