From 78f37a909eb3b7cfe65b8146a59d104e811ed93b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 23 Jul 2020 11:02:28 +0200 Subject: [PATCH] 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 = "