From 91337150e77b1eba5a2f7f2f8d7e7c0357d413fe Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 2 Jul 2020 13:07:17 +0200 Subject: [PATCH 01/14] [sasl] Rename getChannelBindingName() to getGs2CbindFlag() As this returns the value of the SCRAM gs2-cbind-flag. --- .../jivesoftware/smack/sasl/core/ScramMechanism.java | 12 +++++++++--- .../smack/sasl/core/ScramPlusMechanism.java | 4 ++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramMechanism.java b/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramMechanism.java index 6fadfd3a8..bf8a4a224 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramMechanism.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramMechanism.java @@ -1,6 +1,6 @@ /** * - * Copyright 2014-2019 Florian Schmaus + * Copyright 2014-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -227,7 +227,7 @@ public abstract class ScramMechanism extends SASLMechanism { authzidPortion = "a=" + authorizationId; } - String cbName = getChannelBindingName(); + String cbName = getGs2CbindFlag(); assert StringUtils.isNotEmpty(cbName); return cbName + ',' + authzidPortion + ","; @@ -244,7 +244,13 @@ public abstract class ScramMechanism extends SASLMechanism { return ByteUtils.concat(gs2Header, cbindData); } - protected String getChannelBindingName() { + /** + * Get the SCRAM GSS-API Channel Binding Flag value. + * + * @return the gs2-cbind-flag value. + * @see RFC 5802 § 6. + */ + protected String getGs2CbindFlag() { // Check if we are using TLS and if a "-PLUS" variant of this mechanism is enabled. Assuming that the "-PLUS" // variants always have precedence before the non-"-PLUS" variants this means that the server did not announce // the "-PLUS" variant, as otherwise we would have tried it. diff --git a/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramPlusMechanism.java b/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramPlusMechanism.java index 0aa7eee3f..849eee55d 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramPlusMechanism.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/ScramPlusMechanism.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016-2019 Florian Schmaus + * Copyright 2016-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,7 @@ public abstract class ScramPlusMechanism extends ScramMechanism { } @Override - protected String getChannelBindingName() { + protected String getGs2CbindFlag() { return "p=tls-server-end-point"; } From 00acdfcb9ebb9a9a06ade44310b0524a613af738 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 3 Jul 2020 00:24:05 +0200 Subject: [PATCH 02/14] Add QName field in OriginIdElement --- .../org/jivesoftware/smackx/sid/element/OriginIdElement.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/OriginIdElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/OriginIdElement.java index 1a448b516..d5f6935da 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/OriginIdElement.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/OriginIdElement.java @@ -16,6 +16,8 @@ */ package org.jivesoftware.smackx.sid.element; +import javax.xml.namespace.QName; + import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.MessageBuilder; import org.jivesoftware.smack.util.XmlStringBuilder; @@ -25,6 +27,8 @@ import org.jivesoftware.smackx.sid.StableUniqueStanzaIdManager; public class OriginIdElement extends StableAndUniqueIdElement { public static final String ELEMENT = "origin-id"; + public static final String NAMESPACE = StableUniqueStanzaIdManager.NAMESPACE; + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); public OriginIdElement() { super(); From d3a99a133ae39bb822c1daa047e4ca22ebfa6228 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 13 Jul 2020 08:50:10 +0200 Subject: [PATCH 03/14] [core] Fix log/exception message of XmppElementUtil The Class.toString() already prefixes the resulting string with "class ", no need to state it explicitly in the log message that this is a class. --- .../java/org/jivesoftware/smack/util/XmppElementUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/XmppElementUtil.java b/smack-core/src/main/java/org/jivesoftware/smack/util/XmppElementUtil.java index 34ccb0d81..b78f62ab9 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/XmppElementUtil.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/XmppElementUtil.java @@ -37,7 +37,7 @@ public class XmppElementUtil { } LOGGER.warning("The QNAME field of " + fullyQualifiedElement + " is not of type QNAME."); } catch (NoSuchFieldException e) { - LOGGER.finer("The class " + fullyQualifiedElement + " has no static QNAME field. Consider adding one."); + LOGGER.finer("The " + fullyQualifiedElement + " has no static QNAME field. Consider adding one."); // Proceed to fallback strategy. } catch (IllegalArgumentException | IllegalAccessException | SecurityException e) { throw new IllegalArgumentException(e); @@ -49,7 +49,7 @@ public class XmppElementUtil { namespace = (String) fullyQualifiedElement.getField("NAMESPACE").get(null); } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) { - throw new IllegalArgumentException("The class" + fullyQualifiedElement + " has no ELEMENT, NAMESPACE or QNAME member. Consider adding QNAME", e); + throw new IllegalArgumentException("The " + fullyQualifiedElement + " has no ELEMENT, NAMESPACE or QNAME member. Consider adding QNAME", e); } return new QName(namespace, element); From a587827ef8f646ffad3e7db67d95836e58bf16fb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Jul 2020 18:08:18 +0200 Subject: [PATCH 04/14] Remove unnecessary lines from OX backup restore routine --- .../org/jivesoftware/smackx/ox/OpenPgpManager.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java index ce49dbde6..5fa897255 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java @@ -20,7 +20,6 @@ import static org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil.PEP_NODE_PUBLIC_ import static org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil.PEP_NODE_PUBLIC_KEYS_NOTIFY; import static org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil.publishPublicKey; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; @@ -76,12 +75,9 @@ import org.jivesoftware.smackx.pubsub.PubSubException; import org.jivesoftware.smackx.pubsub.PubSubFeature; import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; -import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; import org.jxmpp.jid.BareJid; import org.jxmpp.jid.EntityBareJid; import org.pgpainless.key.OpenPgpV4Fingerprint; @@ -482,13 +478,6 @@ public final class OpenPgpManager extends Manager { provider.getStore().importSecretKey(getJidOrThrow(), secretKeys); provider.getStore().importPublicKey(getJidOrThrow(), BCUtil.publicKeyRingFromSecretKeyRing(secretKeys)); - ByteArrayOutputStream buffer = new ByteArrayOutputStream(2048); - for (PGPSecretKey sk : secretKeys) { - PGPPublicKey pk = sk.getPublicKey(); - if (pk != null) pk.encode(buffer); - } - PGPPublicKeyRing publicKeys = new PGPPublicKeyRing(buffer.toByteArray(), new BcKeyFingerprintCalculator()); - provider.getStore().importPublicKey(getJidOrThrow(), publicKeys); return new OpenPgpV4Fingerprint(secretKeys); } From bc599a6dd63ad9410f5a7135f97ab2c0dac3cc60 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 18 Jul 2020 12:50:08 +0200 Subject: [PATCH 05/14] Add callback method for when Smack is connecting --- .../jivesoftware/smack/AbstractXMPPConnection.java | 9 +++++++++ .../org/jivesoftware/smack/ConnectionListener.java | 12 +++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java index bbb104fe8..246f05eb2 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java @@ -514,6 +514,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { // Check if not already connected throwAlreadyConnectedExceptionIfAppropriate(); + // Notify connection listeners that we are trying to connect + callConnectionConnectingListener(); + // Reset the connection state initState(); closingStreamReceived = false; @@ -1680,6 +1683,12 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { } } + protected void callConnectionConnectingListener() { + for (ConnectionListener listener : connectionListeners) { + listener.connecting(this); + } + } + protected void callConnectionConnectedListener() { for (ConnectionListener listener : connectionListeners) { listener.connected(this); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/ConnectionListener.java b/smack-core/src/main/java/org/jivesoftware/smack/ConnectionListener.java index 3e85191b9..304c8c75a 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/ConnectionListener.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/ConnectionListener.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software. + * Copyright 2003-2007 Jive Software, 2020 Paul Schaub * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,16 @@ package org.jivesoftware.smack; */ public interface ConnectionListener { + /** + * Notification that the connection is in the process of connecting. + * This method is called when {@link AbstractXMPPConnection#connect()} is executed. + * + * @param connection connection + * @since 4.4 + */ + default void connecting(XMPPConnection connection) { + } + /** * Notification that the connection has been successfully connected to the remote endpoint (e.g. the XMPP server). *

From 15b8a81493a14d58bc68ace499a58d72fa0fee09 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Jul 2020 18:09:14 +0200 Subject: [PATCH 06/14] OX: Trust secret key upon backup import --- .../main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java index 5fa897255..139352c07 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java @@ -42,7 +42,6 @@ import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.util.Async; import org.jivesoftware.smack.util.stringencoder.Base64; import org.jivesoftware.smack.xml.XmlPullParserException; - import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.ox.callback.backup.AskForBackupCodeCallback; import org.jivesoftware.smackx.ox.callback.backup.DisplayBackupCodeCallback; @@ -475,9 +474,11 @@ public final class OpenPgpManager extends Manager { String backupCode = codeCallback.askForBackupCode(); PGPSecretKeyRing secretKeys = SecretKeyBackupHelper.restoreSecretKeyBackup(backup, backupCode); + OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(secretKeys); provider.getStore().importSecretKey(getJidOrThrow(), secretKeys); provider.getStore().importPublicKey(getJidOrThrow(), BCUtil.publicKeyRingFromSecretKeyRing(secretKeys)); + getOpenPgpSelf().trust(fingerprint); return new OpenPgpV4Fingerprint(secretKeys); } From 2f98bb25e2d5815b88e61c426901a7ccf3209f1c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Jul 2020 18:09:53 +0200 Subject: [PATCH 07/14] OX: Fix incorrect documentation URL --- .../java/org/jivesoftware/smackx/ox/util/OpenPgpPubSubUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/OpenPgpPubSubUtil.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/OpenPgpPubSubUtil.java index b36ca19f3..0e418d80b 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/OpenPgpPubSubUtil.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/OpenPgpPubSubUtil.java @@ -111,7 +111,7 @@ public class OpenPgpPubSubUtil { * Publish the users OpenPGP public key to the public key node if necessary. * Also announce the key to other users by updating the metadata node. * - * @see XEP-0373 §4.1 + * @see XEP-0373 §4.1 * * @param pepManager The PEP manager. * @param pubkeyElement {@link PubkeyElement} containing the public key From 7d6374b04b0cff761d2fdf730871467015b3031c Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sat, 18 Jul 2020 21:55:47 +0200 Subject: [PATCH 08/14] Bump jXMPP version to 1.0.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b962c9105..399d8864f 100644 --- a/build.gradle +++ b/build.gradle @@ -122,8 +122,8 @@ allprojects { // See also: // - https://issues.apache.org/jira/browse/MNG-6232 // - https://issues.igniterealtime.org/browse/SMACK-858 - jxmppVersion = '0.7.0-alpha6' miniDnsVersion = '0.4.0-alpha6' + jxmppVersion = '1.0.0' smackMinAndroidSdk = 19 junitVersion = '5.6.2' commonsIoVersion = '2.6' From dad2a1ad2efde6934ba33e8baad58622af7ec91b Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sat, 18 Jul 2020 21:55:56 +0200 Subject: [PATCH 09/14] Bump MiniDNS version to 1.0.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 399d8864f..9e4d6651f 100644 --- a/build.gradle +++ b/build.gradle @@ -122,8 +122,8 @@ allprojects { // See also: // - https://issues.apache.org/jira/browse/MNG-6232 // - https://issues.igniterealtime.org/browse/SMACK-858 - miniDnsVersion = '0.4.0-alpha6' jxmppVersion = '1.0.0' + miniDnsVersion = '1.0.0' smackMinAndroidSdk = 19 junitVersion = '5.6.2' commonsIoVersion = '2.6' From bcfe7b12a4b50fb1a482d92f72d47ae14c6eb007 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 20 Jul 2020 14:23:09 +0200 Subject: [PATCH 10/14] [tcp] Mark SM as disabled prior resource binding Otherwise we may send a SM ack request with the bind IQ request, causing a stream error: D/SMACK: SENT (0): D/SMACK: RECV (0): snakeman@wiuwiu.de/eHeTGlCq --- .../java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java index ad86ce420..52ed8a077 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java @@ -407,6 +407,11 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { LOGGER.fine("Stream resumption failed, continuing with normal stream establishment process: " + smResumptionFailed); } + // We either failed to resume a previous stream management (SM) session, or we did not even try. In any case, + // mark SM as not enabled. Most importantly, we do this prior calling bindResourceAndEstablishSession(), as the + // bind IQ may trigger a SM ack request, which would be invalid in the pre resource bound state. + smEnabledSyncPoint = false; + List previouslyUnackedStanzas = new LinkedList(); if (unacknowledgedStanzas != null) { // There was a previous connection with SM enabled but that was either not resumable or @@ -425,7 +430,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { // unacknowledgedStanzas and become duplicated on reconnect. See SMACK-706. bindResourceAndEstablishSession(resource); - smEnabledSyncPoint = false; if (isSmAvailable() && useSm) { // Remove what is maybe left from previously stream managed sessions serverHandledStanzasCount = 0; From fcaeca48ec0eb3848c51ee778ce3626b06c9b7db Mon Sep 17 00:00:00 2001 From: Aditya Borikar Date: Wed, 22 Jul 2020 06:58:23 +0530 Subject: [PATCH 11/14] Add SimpleXmppConnectionIntegrationTest --- .../SimpleXmppConnectionIntegrationTest.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 smack-integration-test/src/main/java/org/jivesoftware/smack/c2s/SimpleXmppConnectionIntegrationTest.java diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smack/c2s/SimpleXmppConnectionIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smack/c2s/SimpleXmppConnectionIntegrationTest.java new file mode 100644 index 000000000..8fc63e13a --- /dev/null +++ b/smack-integration-test/src/main/java/org/jivesoftware/smack/c2s/SimpleXmppConnectionIntegrationTest.java @@ -0,0 +1,67 @@ +/** + * + * Copyright 2020 Aditya Borikar + * + * 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.smack.c2s; + +import java.util.concurrent.TimeoutException; + +import org.jivesoftware.smack.StanzaListener; +import org.jivesoftware.smack.filter.MessageWithBodiesFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Stanza; + +import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; +import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest; +import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint; + +import org.jxmpp.jid.EntityFullJid; + +public class SimpleXmppConnectionIntegrationTest extends AbstractSmackIntegrationTest { + + public SimpleXmppConnectionIntegrationTest(SmackIntegrationTestEnvironment environment) { + super(environment); + } + + @SmackIntegrationTest + public void createConnectionTest() throws TimeoutException, Exception { + EntityFullJid userTwo = conTwo.getUser(); + + final String messageBody = testRunId + ": Hello from the other side!"; + Message message = conTwo.getStanzaFactory().buildMessageStanza() + .to(userTwo) + .setBody(messageBody) + .build(); + + final SimpleResultSyncPoint messageReceived = new SimpleResultSyncPoint(); + + final StanzaListener stanzaListener = (Stanza stanza) -> { + if (((Message) stanza).getBody().equals(messageBody)) { + messageReceived.signal(); + } + }; + + conTwo.addAsyncStanzaListener(stanzaListener, MessageWithBodiesFilter.INSTANCE); + + try { + conOne.sendStanza(message); + + messageReceived.waitForResult(timeout); + } finally { + conTwo.removeAsyncStanzaListener(stanzaListener); + } + } +} From 78f37a909eb3b7cfe65b8146a59d104e811ed93b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 23 Jul 2020 11:02:28 +0200 Subject: [PATCH 12/14] Add support for XEP-0420: Stanza Content Encryption --- documentation/extensions/index.md | 1 + .../element/AffixElement.java | 29 ++ .../element/AffixExtensionElement.java | 32 ++ .../element/ContentElement.java | 288 ++++++++++++++++++ .../element/FromAffixElement.java | 33 ++ .../element/JidAffixElement.java | 57 ++++ .../element/PayloadElement.java | 52 ++++ .../element/RandomPaddingAffixElement.java | 75 +++++ .../element/TimestampAffixElement.java | 63 ++++ .../element/ToAffixElement.java | 34 +++ .../element/package-info.java | 20 ++ .../package-info.java | 22 ++ .../AffixExtensionElementProvider.java | 29 ++ .../provider/ContentElementProvider.java | 137 +++++++++ .../provider/package-info.java | 20 ++ .../experimental.providers | 7 + .../element/AffixElementsTest.java | 156 ++++++++++ .../element/ContentElementTest.java | 81 +++++ .../provider/ContentElementProviderTest.java | 84 +++++ 19 files changed, 1220 insertions(+) create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/AffixElement.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/AffixExtensionElement.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/ContentElement.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/FromAffixElement.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/JidAffixElement.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/PayloadElement.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/RandomPaddingAffixElement.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/TimestampAffixElement.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/ToAffixElement.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/package-info.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/package-info.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/provider/AffixExtensionElementProvider.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/provider/ContentElementProvider.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/provider/package-info.java create mode 100644 smack-experimental/src/test/java/org/jivesoftware/smackx/stanza_content_encryption/element/AffixElementsTest.java create mode 100644 smack-experimental/src/test/java/org/jivesoftware/smackx/stanza_content_encryption/element/ContentElementTest.java create mode 100644 smack-experimental/src/test/java/org/jivesoftware/smackx/stanza_content_encryption/provider/ContentElementProviderTest.java diff --git a/documentation/extensions/index.md b/documentation/extensions/index.md index 78a2bbc57..96501b39f 100644 --- a/documentation/extensions/index.md +++ b/documentation/extensions/index.md @@ -123,6 +123,7 @@ Experimental Smack Extensions and currently supported XEPs of smack-experimental | [Consistent Color Generation](consistent_colors.md) | [XEP-0392](https://xmpp.org/extensions/xep-0392.html) | 0.6.0 | Generate consistent colors for identifiers like usernames to provide a consistent user experience. | | [Message Markup](messagemarkup.md) | [XEP-0394](https://xmpp.org/extensions/xep-0394.html) | 0.1.0 | Style message bodies while keeping body and markup information separated. | | DNS Queries over XMPP (DoX) | [XEP-0418](https://xmpp.org/extensions/xep-0418.html) | 0.1.0 | Send DNS queries and responses over XMPP. | +| Stanza Content Encryption | [XEP-0420](https://xmpp.org/extensions/xep-0420.html) | 0.3.0 | End-to-end encryption of arbitrary extension elements. Smack provides elements and providers to be used by encryption mechanisms. | | Message Fastening | [XEP-0422](https://xmpp.org/extensions/xep-0422.html) | 0.1.1 | Mark payloads on a message to be logistically fastened to a previous message. | | Message Retraction | [XEP-0424](https://xmpp.org/extensions/xep-0424.html) | 0.2.0 | Mark messages as retracted. | | Fallback Indication | [XEP-0428](https://xmpp.org/extensions/xep-0428.html) | 0.1.0 | Declare body elements of a message as ignorable fallback for naive legacy clients. | diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/AffixElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/AffixElement.java new file mode 100644 index 000000000..eb4db3c17 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/AffixElement.java @@ -0,0 +1,29 @@ +/** + * + * Copyright 2020 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.stanza_content_encryption.element; + +import org.jivesoftware.smack.packet.Element; + +/** + * Interface that marks elements that may be used as affix elements inside a {@link ContentElement}. + * + * @see + * XEP-0420: Stanza Content Encryption - §4. Affix Elements + */ +public interface AffixElement extends Element { + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/AffixExtensionElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/AffixExtensionElement.java new file mode 100644 index 000000000..7ff838ae3 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/AffixExtensionElement.java @@ -0,0 +1,32 @@ +/** + * + * Copyright 2020 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.stanza_content_encryption.element; + +import org.jivesoftware.smack.packet.ExtensionElement; + +/** + * Affix element that is identified by element name and namespace. + * You should extend this interface with your custom affix extension elements + * and also provide a {@link org.jivesoftware.smackx.stanza_content_encryption.provider.AffixExtensionElementProvider} + * for them. + * + * @see + * XEP-0420: Stanza Content Encryption - §4. Affix Elements + */ +public interface AffixExtensionElement extends ExtensionElement, AffixElement { + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/ContentElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/ContentElement.java new file mode 100644 index 000000000..1ec2e98c1 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/ContentElement.java @@ -0,0 +1,288 @@ +/** + * + * Copyright 2020 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.stanza_content_encryption.element; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.xml.namespace.QName; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.util.Objects; +import org.jivesoftware.smack.util.XmlStringBuilder; +import org.jivesoftware.smackx.address.packet.MultipleAddresses; +import org.jivesoftware.smackx.hints.element.MessageProcessingHint; +import org.jivesoftware.smackx.sid.element.StanzaIdElement; + +import org.jxmpp.jid.Jid; + +/** + * Extension element that holds the payload element, as well as a list of affix elements. + * In SCE, the XML representation of this element is what will be encrypted using the encryption mechanism of choice. + */ +public class ContentElement implements ExtensionElement { + + private static final String NAMESPACE_UNVERSIONED = "urn:xmpp:sce"; + public static final String NAMESPACE_0 = NAMESPACE_UNVERSIONED + ":0"; + public static final String NAMESPACE = NAMESPACE_0; + public static final String ELEMENT = "content"; + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); + + private final PayloadElement payload; + private final List affixElements; + + ContentElement(PayloadElement payload, List affixElements) { + this.payload = payload; + this.affixElements = Collections.unmodifiableList(affixElements); + } + + /** + * Return the {@link PayloadElement} which holds the sensitive payload extensions. + * + * @return payload element + */ + public PayloadElement getPayload() { + return payload; + } + + /** + * Return a list of affix elements. + * Those are elements that need to be verified upon reception by the encryption mechanisms implementation. + * + * @see + * XEP-0420: Stanza Content Encryption - §4. Affix Elements + * + * @return list of affix elements + */ + public List getAffixElements() { + return affixElements; + } + + @Override + public String getNamespace() { + return NAMESPACE; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { + XmlStringBuilder xml = new XmlStringBuilder(this).rightAngleBracket(); + xml.append(affixElements); + xml.append(payload); + return xml.closeElement(this); + } + + @Override + public QName getQName() { + return QNAME; + } + + /** + * Return a {@link Builder} that can be used to build the {@link ContentElement}. + * @return builder + */ + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private static final Set BLACKLISTED_NAMESPACES = Collections.singleton(MessageProcessingHint.NAMESPACE); + private static final Set BLACKLISTED_QNAMES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + StanzaIdElement.QNAME, + MultipleAddresses.QNAME + ))); + + private FromAffixElement from = null; + private TimestampAffixElement timestamp = null; + private RandomPaddingAffixElement rpad = null; + + private final List otherAffixElements = new ArrayList<>(); + private final List payloadItems = new ArrayList<>(); + + private Builder() { + + } + + /** + * Add an affix element of type 'to' which addresses one recipient. + * The jid in the 'to' element SHOULD be a bare jid. + * + * @param jid jid + * @return builder + */ + public Builder addTo(Jid jid) { + return addTo(new ToAffixElement(jid)); + } + + /** + * Add an affix element of type 'to' which addresses one recipient. + * + * @param to affix element + * @return builder + */ + public Builder addTo(ToAffixElement to) { + this.otherAffixElements.add(Objects.requireNonNull(to, "'to' affix element MUST NOT be null.")); + return this; + } + + /** + * Set the senders jid as a 'from' affix element. + * + * @param jid jid of the sender + * @return builder + */ + public Builder setFrom(Jid jid) { + return setFrom(new FromAffixElement(jid)); + } + + /** + * Set the senders jid as a 'from' affix element. + * + * @param from affix element + * @return builder + */ + public Builder setFrom(FromAffixElement from) { + this.from = Objects.requireNonNull(from, "'form' affix element MUST NOT be null."); + return this; + } + + /** + * Set the given date as a 'time' affix element. + * + * @param date timestamp as date + * @return builder + */ + public Builder setTimestamp(Date date) { + return setTimestamp(new TimestampAffixElement(date)); + } + + /** + * Set the timestamp of the message as a 'time' affix element. + * + * @param timestamp timestamp affix element + * @return builder + */ + public Builder setTimestamp(TimestampAffixElement timestamp) { + this.timestamp = Objects.requireNonNull(timestamp, "'time' affix element MUST NOT be null."); + return this; + } + + /** + * Set some random length random content padding. + * + * @return builder + */ + public Builder setRandomPadding() { + this.rpad = new RandomPaddingAffixElement(); + return this; + } + + /** + * Set the given string as padding. + * The padding should be of length between 1 and 200 characters. + * + * @param padding padding string + * @return builder + */ + public Builder setRandomPadding(String padding) { + return setRandomPadding(new RandomPaddingAffixElement(padding)); + } + + /** + * Set a padding affix element. + * + * @param padding affix element + * @return builder + */ + public Builder setRandomPadding(RandomPaddingAffixElement padding) { + this.rpad = Objects.requireNonNull(padding, "'rpad' affix element MUST NOT be empty."); + return this; + } + + /** + * Add an additional, SCE profile specific affix element. + * + * @param customAffixElement additional affix element + * @return builder + */ + public Builder addFurtherAffixElement(AffixElement customAffixElement) { + this.otherAffixElements.add(Objects.requireNonNull(customAffixElement, + "Custom affix element MUST NOT be null.")); + return this; + } + + /** + * Add a payload item as child element of the payload element. + * There are some items that are not allowed as payload. + * Adding those will throw an exception. + * + * @see + * XEP-0420: Stanza Content Encryption - §9. Server-processed Elements + * + * @param payloadItem extension element + * @return builder + * @throws IllegalArgumentException in case an extension element from the blacklist is added. + */ + public Builder addPayloadItem(ExtensionElement payloadItem) { + Objects.requireNonNull(payloadItem, "Payload item MUST NOT be null."); + this.payloadItems.add(checkForIllegalPayloadsAndPossiblyThrow(payloadItem)); + return this; + } + + /** + * Construct a content element from this builder. + * + * @return content element + */ + public ContentElement build() { + List allAffixElements = collectAffixElements(); + PayloadElement payloadElement = new PayloadElement(payloadItems); + return new ContentElement(payloadElement, allAffixElements); + } + + private static ExtensionElement checkForIllegalPayloadsAndPossiblyThrow(ExtensionElement payloadItem) { + QName qName = payloadItem.getQName(); + if (BLACKLISTED_QNAMES.contains(qName)) { + throw new IllegalArgumentException("Element identified by " + qName + + " is not allowed as payload item. See https://xmpp.org/extensions/xep-0420.html#server-processed"); + } + + String namespace = payloadItem.getNamespace(); + if (BLACKLISTED_NAMESPACES.contains(namespace)) { + throw new IllegalArgumentException("Elements of namespace '" + namespace + + "' are not allowed as payload items. See https://xmpp.org/extensions/xep-0420.html#server-processed"); + } + + return payloadItem; + } + + private List collectAffixElements() { + List allAffixElements = new ArrayList<>(Arrays.asList(rpad, from, timestamp)); + allAffixElements.addAll(otherAffixElements); + return allAffixElements; + } + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/FromAffixElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/FromAffixElement.java new file mode 100644 index 000000000..6af403fae --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/FromAffixElement.java @@ -0,0 +1,33 @@ +/** + * + * Copyright 2020 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.stanza_content_encryption.element; + +import org.jxmpp.jid.Jid; + +public class FromAffixElement extends JidAffixElement { + + public static final String ELEMENT = "from"; + + public FromAffixElement(Jid jid) { + super(jid); + } + + @Override + public String getElementName() { + return ELEMENT; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/JidAffixElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/JidAffixElement.java new file mode 100644 index 000000000..1543d044e --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/JidAffixElement.java @@ -0,0 +1,57 @@ +/** + * + * Copyright 2020 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.stanza_content_encryption.element; + +import org.jivesoftware.smack.packet.NamedElement; +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.util.EqualsUtil; +import org.jivesoftware.smack.util.Objects; +import org.jivesoftware.smack.util.XmlStringBuilder; + +import org.jxmpp.jid.Jid; + +public abstract class JidAffixElement implements NamedElement, AffixElement { + + public static final String ATTR_JID = "jid"; + + private final Jid jid; + + public JidAffixElement(Jid jid) { + this.jid = Objects.requireNonNull(jid, "Value of 'jid' MUST NOT be null."); + } + + public Jid getJid() { + return jid; + } + + @Override + public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { + return new XmlStringBuilder(this) + .attribute(ATTR_JID, getJid()) + .closeEmptyElement(); + } + + @Override + public final boolean equals(Object obj) { + return EqualsUtil.equals(this, obj, (e, o) -> e.append(getJid(), o.getJid()).append(getElementName(), o.getElementName())); + } + + @Override + public final int hashCode() { + return (getElementName() + getJid().toString()).hashCode(); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/PayloadElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/PayloadElement.java new file mode 100644 index 000000000..c3a3431ec --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/PayloadElement.java @@ -0,0 +1,52 @@ +/** + * + * Copyright 2020 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.stanza_content_encryption.element; + +import java.util.Collections; +import java.util.List; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.packet.NamedElement; +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.util.XmlStringBuilder; + +public class PayloadElement implements NamedElement { + + public static final String ELEMENT = "payload"; + + private final List payloadElements; + + public PayloadElement(List payloadElements) { + this.payloadElements = Collections.unmodifiableList(payloadElements); + } + + public List getItems() { + return payloadElements; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { + XmlStringBuilder xml = new XmlStringBuilder(this).rightAngleBracket(); + xml.append(payloadElements); + return xml.closeElement(this); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/RandomPaddingAffixElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/RandomPaddingAffixElement.java new file mode 100644 index 000000000..df43ad456 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/RandomPaddingAffixElement.java @@ -0,0 +1,75 @@ +/** + * + * Copyright 2020 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.stanza_content_encryption.element; + +import java.security.SecureRandom; + +import org.jivesoftware.smack.packet.NamedElement; +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.util.EqualsUtil; +import org.jivesoftware.smack.util.RandomUtil; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.XmlStringBuilder; + +public class RandomPaddingAffixElement implements NamedElement, AffixElement { + + private static final int minPaddingLength = 1; + private static final int maxPaddingLength = 200; + public static final String ELEMENT = "rpad"; + + private final String padding; + + public RandomPaddingAffixElement(String padding) { + this.padding = StringUtils.escapeForXmlText( + StringUtils.requireNotNullNorEmpty(padding, "Value of 'rpad' MUST NOT be null nor empty.")) + .toString(); + } + + public RandomPaddingAffixElement() { + this(StringUtils.randomString(randomPaddingLength(), new SecureRandom())); + } + + private static int randomPaddingLength() { + return minPaddingLength + RandomUtil.nextSecureRandomInt(maxPaddingLength - minPaddingLength); + } + + public String getPadding() { + return padding; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { + return new XmlStringBuilder(this).rightAngleBracket() + .append(getPadding()) + .closeElement(this); + } + + @Override + public boolean equals(Object obj) { + return EqualsUtil.equals(this, obj, (e, o) -> e.append(getPadding(), o.getPadding())); + } + + @Override + public int hashCode() { + return getPadding().hashCode(); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/TimestampAffixElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/TimestampAffixElement.java new file mode 100644 index 000000000..0c48f7b7a --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/TimestampAffixElement.java @@ -0,0 +1,63 @@ +/** + * + * Copyright 2020 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.stanza_content_encryption.element; + +import java.util.Date; + +import org.jivesoftware.smack.packet.NamedElement; +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.util.EqualsUtil; +import org.jivesoftware.smack.util.Objects; +import org.jivesoftware.smack.util.XmlStringBuilder; + +public class TimestampAffixElement implements NamedElement, AffixElement { + + public static final String ELEMENT = "time"; + public static final String ATTR_STAMP = "stamp"; + + private final Date timestamp; + + public TimestampAffixElement(Date timestamp) { + this.timestamp = Objects.requireNonNull(timestamp, "Date must not be null."); + } + + public Date getTimestamp() { + return timestamp; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public CharSequence toXML(XmlEnvironment xmlEnvironment) { + return new XmlStringBuilder(this) + .attribute(ATTR_STAMP, getTimestamp()) + .closeEmptyElement(); + } + + @Override + public boolean equals(Object obj) { + return EqualsUtil.equals(this, obj, (e, o) -> e.append(getTimestamp(), o.getTimestamp())); + } + + @Override + public int hashCode() { + return timestamp.hashCode(); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/ToAffixElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/ToAffixElement.java new file mode 100644 index 000000000..896d0548a --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/ToAffixElement.java @@ -0,0 +1,34 @@ +/** + * + * Copyright 2020 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.stanza_content_encryption.element; + +import org.jxmpp.jid.Jid; + +public class ToAffixElement extends JidAffixElement { + + public static final String ELEMENT = "to"; + + public ToAffixElement(Jid jid) { + super(jid); + } + + @Override + public String getElementName() { + return ELEMENT; + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/package-info.java new file mode 100644 index 000000000..7fc4a033f --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/element/package-info.java @@ -0,0 +1,20 @@ +/** + * + * 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. + */ +/** + * Smack's API for XEP-0420: Stanza Content Encryption: Element classes. + */ +package org.jivesoftware.smackx.stanza_content_encryption.element; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/package-info.java new file mode 100644 index 000000000..938a0af63 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/package-info.java @@ -0,0 +1,22 @@ +/** + * + * 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. + */ +/** + * Smack's API for XEP-0420: Stanza Content Encryption. + * + * @see XEP-0420: Stanza Content Encryption + */ +package org.jivesoftware.smackx.stanza_content_encryption; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/provider/AffixExtensionElementProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/provider/AffixExtensionElementProvider.java new file mode 100644 index 000000000..be4ffe19d --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/provider/AffixExtensionElementProvider.java @@ -0,0 +1,29 @@ +/** + * + * Copyright 2020 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.stanza_content_encryption.provider; + +import org.jivesoftware.smack.provider.ExtensionElementProvider; +import org.jivesoftware.smackx.stanza_content_encryption.element.AffixExtensionElement; + +/** + * Abstract class that needs to be extended by provider classes that parse out affix extension elements. + * + * @param affix extension element. + */ +public abstract class AffixExtensionElementProvider extends ExtensionElementProvider { + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/provider/ContentElementProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/provider/ContentElementProvider.java new file mode 100644 index 000000000..e97b71e7a --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/provider/ContentElementProvider.java @@ -0,0 +1,137 @@ +/** + * + * Copyright 2020 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.stanza_content_encryption.provider; + +import java.io.IOException; +import java.util.Date; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.parsing.SmackParsingException; +import org.jivesoftware.smack.provider.ExtensionElementProvider; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smack.util.ParserUtils; +import org.jivesoftware.smack.xml.XmlPullParser; +import org.jivesoftware.smack.xml.XmlPullParserException; +import org.jivesoftware.smackx.stanza_content_encryption.element.AffixElement; +import org.jivesoftware.smackx.stanza_content_encryption.element.ContentElement; +import org.jivesoftware.smackx.stanza_content_encryption.element.FromAffixElement; +import org.jivesoftware.smackx.stanza_content_encryption.element.PayloadElement; +import org.jivesoftware.smackx.stanza_content_encryption.element.RandomPaddingAffixElement; +import org.jivesoftware.smackx.stanza_content_encryption.element.TimestampAffixElement; +import org.jivesoftware.smackx.stanza_content_encryption.element.ToAffixElement; + +import org.jxmpp.jid.impl.JidCreate; +import org.jxmpp.stringprep.XmppStringprepException; + +public class ContentElementProvider extends ExtensionElementProvider { + + @Override + public ContentElement parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) + throws XmlPullParserException, IOException, SmackParsingException { + ContentElement.Builder builder = ContentElement.builder(); + + while (true) { + XmlPullParser.Event tag = parser.next(); + if (tag == XmlPullParser.Event.START_ELEMENT) { + String name = parser.getName(); + switch (name) { + case ToAffixElement.ELEMENT: + parseToAffix(parser, builder); + break; + + case FromAffixElement.ELEMENT: + parseFromAffix(parser, builder); + break; + + case TimestampAffixElement.ELEMENT: + parseTimestampAffix(parser, builder); + break; + + case RandomPaddingAffixElement.ELEMENT: + parseRPadAffix(parser, builder); + break; + + case PayloadElement.ELEMENT: + parsePayload(parser, xmlEnvironment, builder); + break; + + default: + parseCustomAffix(parser, xmlEnvironment, builder); + break; + } + } else if (tag == XmlPullParser.Event.END_ELEMENT) { + if (parser.getDepth() == initialDepth) { + break; + } + } + } + return builder.build(); + } + + private static void parseCustomAffix(XmlPullParser parser, XmlEnvironment outerXmlEnvironment, ContentElement.Builder builder) + throws XmlPullParserException, IOException, SmackParsingException { + String name = parser.getName(); + String namespace = parser.getNamespace(); + + AffixElement element = (AffixElement) PacketParserUtils.parseExtensionElement(name, namespace, parser, outerXmlEnvironment); + builder.addFurtherAffixElement(element); + } + + private static void parsePayload(XmlPullParser parser, XmlEnvironment outerXmlEnvironment, ContentElement.Builder builder) + throws IOException, XmlPullParserException, SmackParsingException { + final int initialDepth = parser.getDepth(); + while (true) { + XmlPullParser.Event tag = parser.next(); + + if (tag == XmlPullParser.Event.START_ELEMENT) { + String name = parser.getName(); + String namespace = parser.getNamespace(); + ExtensionElement element = PacketParserUtils.parseExtensionElement(name, namespace, parser, outerXmlEnvironment); + builder.addPayloadItem(element); + } + + if (tag == XmlPullParser.Event.END_ELEMENT && parser.getDepth() == initialDepth) { + return; + } + } + } + + private static void parseRPadAffix(XmlPullParser parser, ContentElement.Builder builder) + throws IOException, XmlPullParserException { + builder.setRandomPadding(parser.nextText()); + } + + private static void parseTimestampAffix(XmlPullParser parser, ContentElement.Builder builder) + throws SmackParsingException.SmackTextParseException { + Date timestamp = ParserUtils.getDateFromXep82String( + parser.getAttributeValue("", TimestampAffixElement.ATTR_STAMP)); + builder.setTimestamp(timestamp); + } + + private static void parseFromAffix(XmlPullParser parser, ContentElement.Builder builder) + throws XmppStringprepException { + String jidString = parser.getAttributeValue("", FromAffixElement.ATTR_JID); + builder.setFrom(JidCreate.from(jidString)); + } + + private static void parseToAffix(XmlPullParser parser, ContentElement.Builder builder) + throws XmppStringprepException { + String jidString = parser.getAttributeValue("", ToAffixElement.ATTR_JID); + builder.addTo(JidCreate.from(jidString)); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/provider/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/provider/package-info.java new file mode 100644 index 000000000..450e2c53b --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/stanza_content_encryption/provider/package-info.java @@ -0,0 +1,20 @@ +/** + * + * 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. + */ +/** + * Smack's API for XEP-0420: Stanza Content Encryption: Provider classes. + */ +package org.jivesoftware.smackx.stanza_content_encryption.provider; diff --git a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers index 8e6d3df51..a4d9f15e9 100644 --- a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers +++ b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers @@ -292,6 +292,13 @@ org.jivesoftware.smackx.dox.provider.DnsIqProvider + + + content + urn:xmpp:sce:0 + org.jivesoftware.smackx.stanza_content_encryption.provider.ContentElementProvider + + apply-to diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/stanza_content_encryption/element/AffixElementsTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/stanza_content_encryption/element/AffixElementsTest.java new file mode 100644 index 000000000..2d7fcec0d --- /dev/null +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/stanza_content_encryption/element/AffixElementsTest.java @@ -0,0 +1,156 @@ +/** + * + * Copyright 2020 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.stanza_content_encryption.element; + +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.text.ParseException; +import java.util.Date; + +import org.junit.jupiter.api.Test; +import org.jxmpp.jid.EntityBareJid; +import org.jxmpp.jid.impl.JidCreate; +import org.jxmpp.util.XmppDateTime; + +public class AffixElementsTest { + + public static final EntityBareJid JID_HOUSTON = JidCreate.entityBareFromOrThrowUnchecked("missioncontrol@houston.nasa.gov"); + public static final EntityBareJid JID_OPPORTUNITY = JidCreate.entityBareFromOrThrowUnchecked("opportunity@mars.planet"); + + /** + * Test serialization of 'to' affix element. + * + * @see XEP-420 Example 1 + */ + @Test + public void testToAffixElement() { + ToAffixElement to = new ToAffixElement(JID_HOUSTON); + String expectedXml = ""; + + assertXmlSimilar(expectedXml, to.toXML()); + assertEquals(JID_HOUSTON, to.getJid()); + } + + @Test + public void testToAffixElementEquals() { + ToAffixElement to1 = new ToAffixElement(JID_HOUSTON); + ToAffixElement to2 = new ToAffixElement(JID_HOUSTON); + + assertEquals(to1, to2); + assertEquals(to1, to1); + assertEquals(to1.hashCode(), to2.hashCode()); + assertFalse(to1.equals(null)); + } + + @Test + public void toElementNullArgThrows() { + assertThrows(IllegalArgumentException.class, () -> new ToAffixElement(null)); + } + + /** + * Test serialization of 'from' affix element. + * + * @see XEP-420 Example 1 + */ + @Test + public void testFromAffixElement() { + FromAffixElement from = new FromAffixElement(JID_OPPORTUNITY); + String expectedXml = ""; + + assertXmlSimilar(expectedXml, from.toXML()); + assertEquals(JID_OPPORTUNITY, from.getJid()); + } + + @Test + public void testFromAffixElementEquals() { + FromAffixElement from1 = new FromAffixElement(JID_HOUSTON); + FromAffixElement from2 = new FromAffixElement(JID_HOUSTON); + + assertEquals(from1, from2); + assertEquals(from1, from1); + assertEquals(from1.hashCode(), from2.hashCode()); + assertFalse(from1.equals(null)); + } + + @Test + public void fromElementNullArgThrows() { + assertThrows(IllegalArgumentException.class, () -> new FromAffixElement(null)); + } + + @Test + public void testTimestampAffixElement() throws ParseException { + Date date = XmppDateTime.parseDate("2004-01-25T05:05:00.000+00:00"); + TimestampAffixElement timestamp = new TimestampAffixElement(date); + String expectedXml = "