mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-23 20:42:06 +01:00
Several changes
Add encrypted message listeners Automatically decrypt incoming messages Add convenient methods to get payload
This commit is contained in:
parent
e8f09fc842
commit
ffc8cbbf79
6 changed files with 142 additions and 69 deletions
|
@ -127,7 +127,7 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException | PGPException e) {
|
} 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;
|
return availableKeyPairs;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,12 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.ox;
|
package org.jivesoftware.smackx.ox;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import org.jivesoftware.smack.Manager;
|
import org.jivesoftware.smack.Manager;
|
||||||
|
@ -26,11 +30,20 @@ import org.jivesoftware.smack.XMPPConnection;
|
||||||
import org.jivesoftware.smack.XMPPException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
import org.jivesoftware.smack.chat2.Chat;
|
import org.jivesoftware.smack.chat2.Chat;
|
||||||
import org.jivesoftware.smack.chat2.ChatManager;
|
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.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.exception.SmackOpenPgpException;
|
||||||
|
import org.jivesoftware.smackx.ox.listener.OpenPgpEncryptedMessageListener;
|
||||||
|
|
||||||
import org.jxmpp.jid.BareJid;
|
import org.jxmpp.jid.BareJid;
|
||||||
import org.jxmpp.jid.EntityBareJid;
|
import org.jxmpp.jid.EntityBareJid;
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entry point of Smacks API for XEP-0374: OpenPGP for XMPP: Instant Messaging.
|
* 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 OpenPgpManager openPgpManager;
|
||||||
private final ChatManager chatManager;
|
private final ChatManager chatManager;
|
||||||
|
|
||||||
private OXInstantMessagingManager(XMPPConnection connection) {
|
private final Set<OpenPgpEncryptedMessageListener> chatMessageListeners = new HashSet<>();
|
||||||
|
|
||||||
|
private OXInstantMessagingManager(final XMPPConnection connection) {
|
||||||
super(connection);
|
super(connection);
|
||||||
this.openPgpManager = OpenPgpManager.getInstanceFor(connection);
|
this.openPgpManager = OpenPgpManager.getInstanceFor(connection);
|
||||||
this.chatManager = ChatManager.getInstanceFor(connection);
|
this.chatManager = ChatManager.getInstanceFor(connection);
|
||||||
|
chatManager.addIncomingListener(incomingChatMessageListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OXInstantMessagingManager getInstanceFor(XMPPConnection connection) {
|
public static OXInstantMessagingManager getInstanceFor(XMPPConnection connection) {
|
||||||
|
@ -91,15 +107,10 @@ public final class OXInstantMessagingManager extends Manager {
|
||||||
* @throws XMPPException.XMPPErrorException
|
* @throws XMPPException.XMPPErrorException
|
||||||
* @throws SmackException.NotConnectedException
|
* @throws SmackException.NotConnectedException
|
||||||
* @throws SmackException.NoResponseException
|
* @throws SmackException.NoResponseException
|
||||||
* @throws SmackException.FeatureNotSupportedException if the contact does not announce support for XEP-0374.
|
|
||||||
*/
|
*/
|
||||||
public OpenPgpEncryptedChat chatWith(EntityBareJid jid)
|
public OpenPgpEncryptedChat chatWith(EntityBareJid jid)
|
||||||
throws SmackOpenPgpException, InterruptedException, XMPPException.XMPPErrorException,
|
throws SmackOpenPgpException, InterruptedException, XMPPException.XMPPErrorException,
|
||||||
SmackException.NotConnectedException, SmackException.NoResponseException,
|
SmackException.NotConnectedException, SmackException.NoResponseException {
|
||||||
SmackException.FeatureNotSupportedException {
|
|
||||||
if (!contactSupportsOxInstantMessaging(jid)) {
|
|
||||||
throw new SmackException.FeatureNotSupportedException(NAMESPACE_0, jid);
|
|
||||||
}
|
|
||||||
|
|
||||||
OpenPgpFingerprints theirKeys = openPgpManager.determineContactsKeys(jid);
|
OpenPgpFingerprints theirKeys = openPgpManager.determineContactsKeys(jid);
|
||||||
OpenPgpFingerprints ourKeys = openPgpManager.determineContactsKeys(connection().getUser().asBareJid());
|
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);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.ox.element;
|
package org.jivesoftware.smackx.ox.element;
|
||||||
|
|
||||||
|
import static org.jivesoftware.smack.util.StringUtils.requireNotNullOrEmpty;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
@ -24,10 +26,13 @@ import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
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.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
import org.jxmpp.jid.Jid;
|
import org.jxmpp.jid.Jid;
|
||||||
import org.jxmpp.util.XmppDateTime;
|
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
|
* 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<Jid> to;
|
private final Set<Jid> to;
|
||||||
private final Date timestamp;
|
private final Date timestamp;
|
||||||
private final List<ExtensionElement> payload;
|
private final MultiMap<String, ExtensionElement> payload;
|
||||||
|
|
||||||
private String timestampString;
|
private String timestampString;
|
||||||
|
|
||||||
protected OpenPgpContentElement(Set<Jid> to, Date timestamp, List<ExtensionElement> payload) {
|
protected OpenPgpContentElement(Set<Jid> to, Date timestamp, List<ExtensionElement> payload) {
|
||||||
this.to = to;
|
this.to = to;
|
||||||
this.timestamp = timestamp;
|
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 the payload of the message.
|
||||||
* @return payload.
|
* @return payload.
|
||||||
*/
|
*/
|
||||||
public final List<ExtensionElement> getPayload() {
|
public final List<ExtensionElement> getExtensions() {
|
||||||
return payload;
|
synchronized (payload) {
|
||||||
|
return payload.values();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a list of all extensions with the given element name <em>and</em> namespace.
|
||||||
|
* <p>
|
||||||
|
* Changes to the returned set will update the stanza extensions, if the returned set is not the empty set.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @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<ExtensionElement> 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.
|
||||||
|
* <p>
|
||||||
|
* When possible, use {@link #getExtension(String,String)} instead.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @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 <tt>null</tt> 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 <PE> type of the ExtensionElement.
|
||||||
|
* @return the extension, or <tt>null</tt> if it doesn't exist.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <PE extends ExtensionElement> 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
|
@Override
|
||||||
public String getNamespace() {
|
public String getNamespace() {
|
||||||
return OpenPgpElement.NAMESPACE;
|
return OpenPgpElement.NAMESPACE;
|
||||||
|
@ -97,7 +166,7 @@ public abstract class OpenPgpContentElement implements ExtensionElement {
|
||||||
xml.halfOpenElement(ELEM_TIME).attribute(ATTR_STAMP, timestampString).closeEmptyElement();
|
xml.halfOpenElement(ELEM_TIME).attribute(ATTR_STAMP, timestampString).closeEmptyElement();
|
||||||
|
|
||||||
xml.openElement(ELEM_PAYLOAD);
|
xml.openElement(ELEM_PAYLOAD);
|
||||||
for (ExtensionElement element : payload) {
|
for (ExtensionElement element : payload.values()) {
|
||||||
xml.append(element.toXML(getNamespace()));
|
xml.append(element.toXML(getNamespace()));
|
||||||
}
|
}
|
||||||
xml.closeElement(ELEM_PAYLOAD);
|
xml.closeElement(ELEM_PAYLOAD);
|
||||||
|
|
|
@ -22,7 +22,7 @@ import org.jivesoftware.smackx.ox.element.SigncryptElement;
|
||||||
|
|
||||||
import org.jxmpp.jid.EntityBareJid;
|
import org.jxmpp.jid.EntityBareJid;
|
||||||
|
|
||||||
public interface OXEncryptedChatMessageListener {
|
public interface OpenPgpEncryptedMessageListener {
|
||||||
|
|
||||||
void newIncomingEncryptedMessage(EntityBareJid from,
|
void newIncomingEncryptedMessage(EntityBareJid from,
|
||||||
Message originalMessage,
|
Message originalMessage,
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -108,7 +108,7 @@ public class OpenPgpElementTest extends SmackTestSuite {
|
||||||
|
|
||||||
assertEquals(element.getTimestamp(), parsed.getTimestamp());
|
assertEquals(element.getTimestamp(), parsed.getTimestamp());
|
||||||
assertEquals(element.getTo(), parsed.getTo());
|
assertEquals(element.getTo(), parsed.getTo());
|
||||||
assertEquals(element.getPayload(), parsed.getPayload());
|
assertEquals(element.getExtensions(), parsed.getExtensions());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -138,7 +138,7 @@ public class OpenPgpElementTest extends SmackTestSuite {
|
||||||
|
|
||||||
assertEquals(element.getTimestamp(), parsed.getTimestamp());
|
assertEquals(element.getTimestamp(), parsed.getTimestamp());
|
||||||
assertEquals(element.getTo(), parsed.getTo());
|
assertEquals(element.getTo(), parsed.getTo());
|
||||||
assertEquals(element.getPayload(), parsed.getPayload());
|
assertEquals(element.getExtensions(), parsed.getExtensions());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -168,7 +168,7 @@ public class OpenPgpElementTest extends SmackTestSuite {
|
||||||
|
|
||||||
assertEquals(element.getTimestamp(), parsed.getTimestamp());
|
assertEquals(element.getTimestamp(), parsed.getTimestamp());
|
||||||
assertEquals(element.getTo(), parsed.getTo());
|
assertEquals(element.getTo(), parsed.getTo());
|
||||||
assertEquals(element.getPayload(), parsed.getPayload());
|
assertEquals(element.getExtensions(), parsed.getExtensions());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue