ContentElement.java

  1. /**
  2.  *
  3.  * Copyright 2020 Paul Schaub
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smackx.stanza_content_encryption.element;

  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collections;
  21. import java.util.Date;
  22. import java.util.HashSet;
  23. import java.util.List;
  24. import java.util.Set;

  25. import javax.xml.namespace.QName;

  26. import org.jivesoftware.smack.packet.ExtensionElement;
  27. import org.jivesoftware.smack.packet.XmlElement;
  28. import org.jivesoftware.smack.packet.XmlEnvironment;
  29. import org.jivesoftware.smack.util.Objects;
  30. import org.jivesoftware.smack.util.XmlStringBuilder;

  31. import org.jivesoftware.smackx.address.packet.MultipleAddresses;
  32. import org.jivesoftware.smackx.hints.element.MessageProcessingHint;
  33. import org.jivesoftware.smackx.sid.element.StanzaIdElement;

  34. import org.jxmpp.jid.Jid;

  35. /**
  36.  * Extension element that holds the payload element, as well as a list of affix elements.
  37.  * In SCE, the XML representation of this element is what will be encrypted using the encryption mechanism of choice.
  38.  */
  39. public class ContentElement implements ExtensionElement {

  40.     private static final String NAMESPACE_UNVERSIONED = "urn:xmpp:sce";
  41.     public static final String NAMESPACE_0 = NAMESPACE_UNVERSIONED + ":0";
  42.     public static final String NAMESPACE = NAMESPACE_0;
  43.     public static final String ELEMENT = "content";
  44.     public static final QName QNAME = new QName(NAMESPACE, ELEMENT);

  45.     private final PayloadElement payload;
  46.     private final List<AffixElement> affixElements;

  47.     ContentElement(PayloadElement payload, List<AffixElement> affixElements) {
  48.         this.payload = payload;
  49.         this.affixElements = Collections.unmodifiableList(affixElements);
  50.     }

  51.     /**
  52.      * Return the {@link PayloadElement} which holds the sensitive payload extensions.
  53.      *
  54.      * @return payload element
  55.      */
  56.     public PayloadElement getPayload() {
  57.         return payload;
  58.     }

  59.     /**
  60.      * Return a list of affix elements.
  61.      * Those are elements that need to be verified upon reception by the encryption mechanisms implementation.
  62.      *
  63.      * @see <a href="https://xmpp.org/extensions/xep-0420.html#affix_elements">
  64.      *     XEP-0420: Stanza Content Encryption - §4. Affix Elements</a>
  65.      *
  66.      * @return list of affix elements
  67.      */
  68.     public List<AffixElement> getAffixElements() {
  69.         return affixElements;
  70.     }

  71.     @Override
  72.     public String getNamespace() {
  73.         return NAMESPACE;
  74.     }

  75.     @Override
  76.     public String getElementName() {
  77.         return ELEMENT;
  78.     }

  79.     @Override
  80.     public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
  81.         XmlStringBuilder xml = new XmlStringBuilder(this).rightAngleBracket();
  82.         xml.append(affixElements);
  83.         xml.append(payload);
  84.         return xml.closeElement(this);
  85.     }

  86.     @Override
  87.     public QName getQName() {
  88.         return QNAME;
  89.     }

  90.     /**
  91.      * Return a {@link Builder} that can be used to build the {@link ContentElement}.
  92.      * @return builder
  93.      */
  94.     public static Builder builder() {
  95.         return new Builder();
  96.     }

  97.     public static final class Builder {
  98.         private static final Set<String> BLACKLISTED_NAMESPACES = Collections.singleton(MessageProcessingHint.NAMESPACE);
  99.         private static final Set<QName> BLACKLISTED_QNAMES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
  100.                 StanzaIdElement.QNAME,
  101.                 MultipleAddresses.QNAME
  102.         )));

  103.         private FromAffixElement from = null;
  104.         private TimestampAffixElement timestamp = null;
  105.         private RandomPaddingAffixElement rpad = null;

  106.         private final List<AffixElement> otherAffixElements = new ArrayList<>();
  107.         private final List<XmlElement> payloadItems = new ArrayList<>();

  108.         private Builder() {

  109.         }

  110.         /**
  111.          * Add an affix element of type 'to' which addresses one recipient.
  112.          * The jid in the 'to' element SHOULD be a bare jid.
  113.          *
  114.          * @param jid jid
  115.          * @return builder
  116.          */
  117.         public Builder addTo(Jid jid) {
  118.             return addTo(new ToAffixElement(jid));
  119.         }

  120.         /**
  121.          * Add an affix element of type 'to' which addresses one recipient.
  122.          *
  123.          * @param to affix element
  124.          * @return builder
  125.          */
  126.         public Builder addTo(ToAffixElement to) {
  127.             this.otherAffixElements.add(Objects.requireNonNull(to, "'to' affix element MUST NOT be null."));
  128.             return this;
  129.         }

  130.         /**
  131.          * Set the senders jid as a 'from' affix element.
  132.          *
  133.          * @param jid jid of the sender
  134.          * @return builder
  135.          */
  136.         public Builder setFrom(Jid jid) {
  137.             return setFrom(new FromAffixElement(jid));
  138.         }

  139.         /**
  140.          * Set the senders jid as a 'from' affix element.
  141.          *
  142.          * @param from affix element
  143.          * @return builder
  144.          */
  145.         public Builder setFrom(FromAffixElement from) {
  146.             this.from = Objects.requireNonNull(from, "'form' affix element MUST NOT be null.");
  147.             return this;
  148.         }

  149.         /**
  150.          * Set the given date as a 'time' affix element.
  151.          *
  152.          * @param date timestamp as date
  153.          * @return builder
  154.          */
  155.         public Builder setTimestamp(Date date) {
  156.             return setTimestamp(new TimestampAffixElement(date));
  157.         }

  158.         /**
  159.          * Set the timestamp of the message as a 'time' affix element.
  160.          *
  161.          * @param timestamp timestamp affix element
  162.          * @return builder
  163.          */
  164.         public Builder setTimestamp(TimestampAffixElement timestamp) {
  165.             this.timestamp = Objects.requireNonNull(timestamp, "'time' affix element MUST NOT be null.");
  166.             return this;
  167.         }

  168.         /**
  169.          * Set some random length random content padding.
  170.          *
  171.          * @return builder
  172.          */
  173.         public Builder setRandomPadding() {
  174.             this.rpad = new RandomPaddingAffixElement();
  175.             return this;
  176.         }

  177.         /**
  178.          * Set the given string as padding.
  179.          * The padding should be of length between 1 and 200 characters.
  180.          *
  181.          * @param padding padding string
  182.          * @return builder
  183.          */
  184.         public Builder setRandomPadding(String padding) {
  185.             return setRandomPadding(new RandomPaddingAffixElement(padding));
  186.         }

  187.         /**
  188.          * Set a padding affix element.
  189.          *
  190.          * @param padding affix element
  191.          * @return builder
  192.          */
  193.         public Builder setRandomPadding(RandomPaddingAffixElement padding) {
  194.             this.rpad = Objects.requireNonNull(padding, "'rpad' affix element MUST NOT be empty.");
  195.             return this;
  196.         }

  197.         /**
  198.          * Add an additional, SCE profile specific affix element.
  199.          *
  200.          * @param customAffixElement additional affix element
  201.          * @return builder
  202.          */
  203.         public Builder addFurtherAffixElement(AffixElement customAffixElement) {
  204.             this.otherAffixElements.add(Objects.requireNonNull(customAffixElement,
  205.                     "Custom affix element MUST NOT be null."));
  206.             return this;
  207.         }

  208.         /**
  209.          * Add a payload item as child element of the payload element.
  210.          * There are some items that are not allowed as payload.
  211.          * Adding those will throw an exception.
  212.          *
  213.          * @see <a href="https://xmpp.org/extensions/xep-0420.html#server-processed">
  214.          *     XEP-0420: Stanza Content Encryption - §9. Server-processed Elements</a>
  215.          *
  216.          * @param payloadItem extension element
  217.          * @return builder
  218.          * @throws IllegalArgumentException in case an extension element from the blacklist is added.
  219.          */
  220.         public Builder addPayloadItem(XmlElement payloadItem) {
  221.             Objects.requireNonNull(payloadItem, "Payload item MUST NOT be null.");
  222.             this.payloadItems.add(checkForIllegalPayloadsAndPossiblyThrow(payloadItem));
  223.             return this;
  224.         }

  225.         /**
  226.          * Construct a content element from this builder.
  227.          *
  228.          * @return content element
  229.          */
  230.         public ContentElement build() {
  231.             List<AffixElement> allAffixElements = collectAffixElements();
  232.             PayloadElement payloadElement = new PayloadElement(payloadItems);
  233.             return new ContentElement(payloadElement, allAffixElements);
  234.         }

  235.         private static XmlElement checkForIllegalPayloadsAndPossiblyThrow(XmlElement payloadItem) {
  236.             QName qName = payloadItem.getQName();
  237.             if (BLACKLISTED_QNAMES.contains(qName)) {
  238.                 throw new IllegalArgumentException("Element identified by " + qName +
  239.                         " is not allowed as payload item. See https://xmpp.org/extensions/xep-0420.html#server-processed");
  240.             }

  241.             String namespace = payloadItem.getNamespace();
  242.             if (BLACKLISTED_NAMESPACES.contains(namespace)) {
  243.                 throw new IllegalArgumentException("Elements of namespace '" + namespace +
  244.                         "' are not allowed as payload items. See https://xmpp.org/extensions/xep-0420.html#server-processed");
  245.             }

  246.             return payloadItem;
  247.         }

  248.         private List<AffixElement> collectAffixElements() {
  249.             List<AffixElement> allAffixElements = new ArrayList<>(Arrays.asList(rpad, from, timestamp));
  250.             allAffixElements.addAll(otherAffixElements);
  251.             return allAffixElements;
  252.         }
  253.     }
  254. }