Package org.apache.camel.component.xmlsecurity.api

Source Code of org.apache.camel.component.xmlsecurity.api.DefaultXmlSignature2Message

/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.camel.component.xmlsecurity.api;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dom.DOMStructure;
import javax.xml.crypto.dsig.Manifest;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.Transform;
import javax.xml.crypto.dsig.XMLObject;
import javax.xml.crypto.dsig.XMLSignatureException;
import javax.xml.crypto.dsig.spec.XPathFilterParameterSpec;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import org.apache.camel.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
* Maps the XML signature to a camel message. A output node is determined from
* the XML signature document via a node search and then serialized and set to
* the output message body.
* <p>
* There are three output node search types supported: "Default", "ElementName",
* and "XPath". All these search types support enveloped XML signature or
* enveloping XML signature.
* <p>
* <ul>
* <li>The "ElementName" search uses the local name and namespace specified in
* the search value to determine the output element from the XML signature
* document. With the input parameter 'RemoveSignatureElements", you can specify
* whether the signature elements should be removed from the resulting output
* document. This flag shall be used for enveloped XML signatures.
* <li>The "XPath" search uses an XPath expression to evaluate the output node.
* In this case the output node can be of type Element, TextNode, or Document.
* With the input parameter 'RemoveSignatureElements", you can specify whether
* the signature elements should be removed from the resulting output document.
* This flag shall be used for enveloped XML signatures.
* <li>The "Default" search is explained in more detail below.
* </ul>
* <p>
* Default Output Node Search:
* <ul>
* In the enveloped XML signature case, the XML document without the signature
* part is returned in the message body.
* <p>
* In the enveloping XML signature case, the message body is determined from a
* referenced Object element in the following way:
* <ul>
* <li>Only same document references are taken into account (URI must start with
* '#').
* <li>Also indirect same document references to an object via manifest are
* taken into account.
* <li>The resulting number of object references must be 1.
* <li>The referenced object must contain exactly 1 {@link DOMStructure}.
* <li>The node of the DOMStructure is serialized to a byte array and added as
* body to the message.
* </ul>
* This does mean that the enveloping XML signature must have either the
* structure
*
* <pre>
*     {@code
*     <Signature>
*         <SignedInfo>
*            <Reference URI="#object"/>      
*            <!-- further references possible but they must not point to an Object or Manifest containing an object reference -->
*            ...
*         </SignedInfo>
*    
*         <Object Id="object">
*              <!-- contains the DOM node which should be extracted to the message body -->
*         <Object>
*         <!-- further object elements possible which are not referenced-->
*         ...
*         (<KeyInfo>)?
*     </Signature>
*     }
* </pre>
*
* or the structure
*
* <pre>
*     {@code
*     <Signature>
*         <SignedInfo>
*            <Reference URI="#manifest"/>      
*            <!-- further references  are possible but they must not point to an Object or other manifest containing an object reference -->
*            ...
*         </SignedInfo>
*    
*         <Object >
*            <Manifest Id="manifest">
*               <Reference URI=#object/>
*            </Manifest>
*         </Objet>
*         <Object Id="object">
*             <!-- contains the DOM node which should be extracted to the message body -->
*         </Object>
*          <!-- further object elements possible which are not referenced -->
*         ...
*         (<KeyInfo>)?
*     </Signature>
*     }
* </pre>
* </ul>
*/
public class DefaultXmlSignature2Message implements XmlSignature2Message {

    /**
     * Search type 'Default' for determining the output node.
     *
     */
    public static final String OUTPUT_NODE_SEARCH_TYPE_DEFAULT = "Default";

    /**
     * Search type 'ElementName' for determining the output element.
     *
     */
    public static final String OUTPUT_NODE_SEARCH_TYPE_ELEMENT_NAME = "ElementName";

    /**
     * Search type 'XPath' for determining the output node. Search value must be
     * of type {@link XPathFilterParameterSpec}.
     *
     */
    public static final String OUTPUT_NODE_SEARCH_TYPE_XPATH = "XPath";

    private static final Logger LOG = LoggerFactory.getLogger(DefaultXmlSignature2Message.class);

    @Override
    public void mapToMessage(Input input, Message output) throws Exception { //NOPMD

        Node node;
        boolean removeSignatureElements = false;
        if (OUTPUT_NODE_SEARCH_TYPE_DEFAULT.equals(input.getOutputNodeSearchType())) {
            LOG.debug("Searching for output node via default search");
            if (isEnveloped(input)) {
                // enveloped XML signature --> remove signature element
                node = input.getMessageBodyDocument().getDocumentElement();
                removeSignatureElements = true;
            } else {
                node = getNodeForMessageBodyInNonEnvelopedCase(input);
            }
        } else if (OUTPUT_NODE_SEARCH_TYPE_ELEMENT_NAME.equals(input.getOutputNodeSearchType())) {
            node = getOutputElementViaLocalNameAndNamespace(input);
        } else if (OUTPUT_NODE_SEARCH_TYPE_XPATH.equals(input.getOutputNodeSearchType())) {
            node = getOutputNodeViaXPath(input);
        } else {
            throw new XmlSignatureException(String.format("Wrong configuration: The output node search type %s is not supported.",
                    input.getOutputNodeSearchType()));
        }

        LOG.debug("Output node with local name {} and namespace {} found", node.getLocalName(), node.getNamespaceURI());

        if (!removeSignatureElements) {
            removeSignatureElements = input.getRemoveSignatureElements() != null && input.getRemoveSignatureElements();
        }

        if (removeSignatureElements) {
            removeSignatureElements(node);
        }

        transformNodeToByteArrayAndSetToOutputMessage(input, output, node);
    }

    protected void transformNodeToByteArrayAndSetToOutputMessage(Input input, Message output, Node node)
        throws TransformerFactoryConfigurationError, TransformerConfigurationException, TransformerException, IOException {
       
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        XmlSignatureHelper.transformToOutputStream(node, os, omitXmlDeclaration(output, input));
        output.setBody(os.toByteArray());
    }

    protected Node getOutputNodeViaXPath(Input input) throws Exception { //NOPMD
        checkSearchValueNotNull(input);
        checkSearchValueOfType(XPathFilterParameterSpec.class, input);
        XPathFilterParameterSpec xpathFilter = (XPathFilterParameterSpec) input.getOutputNodeSearch();
        XPathExpression expr = XmlSignatureHelper.getXPathExpression(xpathFilter);
        NodeList nodes = (NodeList) expr.evaluate(input.getMessageBodyDocument(), XPathConstants.NODESET);
        if (nodes == null || nodes.getLength() == 0) {
            throw new XmlSignatureException(
                    String.format(
                            "Cannot extract root node for the output document from the XML signature document. No node found for XPATH %s as specified in the output node search.",
                            xpathFilter.getXPath()));
        }
        if (nodes.getLength() > 1) {
            throw new XmlSignatureException(
                    String.format(
                            "Cannot extract root node for the output document from the XML signature document. XPATH %s as specified in the output node search results into more than one child.",
                            xpathFilter.getXPath()));

        }
        Node result = nodes.item(0);
        if (Node.ELEMENT_NODE == result.getNodeType() || Node.TEXT_NODE == result.getNodeType()
                || Node.DOCUMENT_NODE == result.getNodeType()) {
            return result;
        }
        throw new XmlSignatureException(
                String.format(
                        "Cannot extract root node for the output document from the XML signature document. "
                        + "XPATH %s as specified in the output node search results into a node which has the wrong type.",
                        xpathFilter.getXPath()));
    }

    protected Node getOutputElementViaLocalNameAndNamespace(Input input) throws Exception { //NOPMD
        String search = getNonEmptyStringSearchValue(input);
        String namespace;
        String localName;
        if ('{' == search.charAt(0)) {
            // namespace
            int index = search.indexOf('}');
            if (index < 1) {
                throw new XmlSignatureException(
                        String.format(
                                "Wrong configuration: Value %s for the output node search %s has wrong format. "
                                + "Value must have the form '{<namespace>}<element local name>' or '<element local name>' if no the element has no namespace.",
                                search, input.getOutputNodeSearchType()));
            }
            namespace = search.substring(1, index);
            if (search.length() < index + 1) {
                throw new XmlSignatureException(
                        String.format(
                                "Wrong configuration: Value %s for the output node search %s has wrong format. "
                                + "Value must have the form '{<namespace>}<element local name>' or '<element local name>' if no the element has no namespace.",
                                search, input.getOutputNodeSearchType()));
            }
            localName = search.substring(index + 1);
        } else {
            namespace = null;
            localName = search;
        }
        NodeList nodeList = input.getMessageBodyDocument().getElementsByTagNameNS(namespace, localName);
        if (nodeList.getLength() == 0) {
            throw new XmlSignatureException(
                    String.format(
                            "Cannot extract root element for the output document from the XML signature document. Element with local name %s and namespace %s does not exist.",
                            namespace, localName));
        }
        if (nodeList.getLength() > 1) {
            throw new XmlSignatureException(
                    String.format(
                            "Cannot extract root element for the output document from the XML signature document. More than one element found with local name %s and namespace %s.",
                            namespace, localName));
        }
        return nodeList.item(0);
    }

    protected String getNonEmptyStringSearchValue(Input input) throws Exception { //NOPMD
        checkSearchValueNotNull(input);
        checkSearchValueOfType(String.class, input);
        String search = (String) input.getOutputNodeSearch();
        checkStringSarchValueNotEmpty(search, input.getOutputNodeSearchType());
        return search;
    }

    protected void checkSearchValueOfType(Class<?> cl, Input input) throws Exception { //NOPMD
        if (!cl.isAssignableFrom(input.getOutputNodeSearch().getClass())) {
            throw new XMLSignatureException(String.format(
                    "Wrong configruation: Search value is of class %s, the output node search %s requires class %s.", input
                            .getOutputNodeSearch().getClass().getName(), input.getOutputNodeSearchType(), cl.getName()));
        }

    }

    protected void checkStringSarchValueNotEmpty(String searchValue, String outputNodeSearchType) throws Exception { //NOPMD
        if (searchValue.isEmpty()) {
            throw new XMLSignatureException(String.format("Wrong configruation: Value for output node search %s is empty.",
                    outputNodeSearchType));
        }
    }

    protected void checkSearchValueNotNull(Input input) throws Exception { //NOPMD
        LOG.debug("Searching for output element with search value '{}' and sarch type {}", input.getOutputNodeSearch(),
                input.getOutputNodeSearchType());
        if (input.getOutputNodeSearch() == null) {
            throw new XMLSignatureException(String.format("Wrong configruation: Value is missing for output node search %s.",
                    input.getOutputNodeSearchType()));
        }
    }

    protected Node getNodeForMessageBodyInNonEnvelopedCase(Input input) throws Exception { //NOPMD
        Node node;
        List<Reference> relevantReferences = getReferencesForMessageMapping(input);

        List<XMLObject> relevantObjects = getObjectsForMessageMapping(input);

        DOMStructure domStruc = getDomStructureForMessageBody(relevantReferences, relevantObjects);
        node = domStruc.getNode();
        return node;
    }

    /**
     * Removes the Signature elements from the document.
     *
     * @param doc
     *            document
     */
    protected void removeSignatureElements(Node node) {
        Document doc = XmlSignatureHelper.getDocument(node);
        NodeList nl = doc.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Signature");
        for (int i = 0; i < nl.getLength(); i++) {
            Node n = nl.item(i);
            Node parent = n.getParentNode();
            if (parent != null) {
                parent.removeChild(n);
            }
        }
    }

    /**
     * Returns the enveloped data in case of an enveloped XML signature.
     *
     * @param input
     *            references of signed info and objects
     * @return <code>true</code> if there exists a reference with URI = "" and
     *         with {@link Transform#ENVELOPED} transform; otherwise
     *         <code>false</code>
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    protected boolean isEnveloped(Input input) throws Exception { //NOPMD
        for (Reference ref : input.getReferences()) {
            if ("".equals(ref.getURI())) {
                for (Transform t : (List<Transform>)ref.getTransforms()) {
                    if (Transform.ENVELOPED.equals(t.getAlgorithm())) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    protected Boolean omitXmlDeclaration(Message message, Input input) {
        Boolean omitXmlDeclaration = message.getHeader(XmlSignatureConstants.HEADER_OMIT_XML_DECLARATION, Boolean.class);
        if (omitXmlDeclaration == null) {
            omitXmlDeclaration = input.omitXmlDeclaration();
        }
        if (omitXmlDeclaration == null) {
            omitXmlDeclaration = Boolean.FALSE;
        }
        return omitXmlDeclaration;
    }

    /**
     * Returns the references whose referenced objects are taken into account
     * for the message body. This message you can use to filter the relevant
     * references from the references provided by the input parameter.
     *
     *
     * @param input
     *            references and objects
     * @return relevant references for the mapping to the camel message
     * @throws Exception
     *             if an error occurs
     */
    protected List<Reference> getReferencesForMessageMapping(Input input) throws Exception { //NOPMD
        return input.getReferences();
    }

    /**
     * Returns the objects which must be taken into account for the mapping to
     * the camel message.
     *
     * @param input
     *            references and objects
     * @return relevant objects for the mapping to camel message
     * @throws Exception
     *             if an error occurs
     */
    protected List<XMLObject> getObjectsForMessageMapping(Input input) throws Exception { //NOPMD
        return input.getObjects();
    }

    /**
     * Returns the DOM structure which is transformed to a byte array and set to
     * the camel message body.
     *
     * @param relevantReferences
     *            input from method
     *            {@link #getReferencesForMessageMapping(ReferencesAndObjects)}
     * @param relevantObjects
     *            input from method
     *            {@link #getObjectsForMessageMapping(ReferencesAndObjects)}
     * @return dom structure
     * @throws Exception
     *             if an error occurs
     */
    protected DOMStructure getDomStructureForMessageBody(List<Reference> relevantReferences, List<XMLObject> relevantObjects)
        throws Exception { //NOPMD
       
        List<XMLObject> referencedObjects = getReferencedSameDocumentObjects(relevantReferences, relevantObjects);

        if (referencedObjects.isEmpty()) {
            throw new XmlSignatureException(
                    String.format("Unsupported XML signature document: Content object not found in the XML signature. Detached or enveloped signatures are not supported."));
        }

        if (referencedObjects.size() > 1) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < referencedObjects.size(); i++) {
                XMLObject xmlOb = referencedObjects.get(i);
                sb.append(xmlOb.getId());
                if (i < referencedObjects.size() - 1) {
                    sb.append(", ");
                }
            }
            throw new XmlSignatureException(String.format(
                    "Unsupported XML signature document: More than one content objects found. Object IDs: %s", sb.toString()));
        }

        @SuppressWarnings("unchecked")
        List<XMLStructure> structures = referencedObjects.get(0).getContent();
        if (structures.size() == 0) {
            throw new XmlSignatureException(
                    "Unsupported XML signature: XML signature is not enveloping; content not found in XML signature: structure list is empty.");
        }
        if (structures.size() > 1) {
            throw new XmlSignatureException("Unsupported XML signature: more than one structure elements in referenced content object.");
        }
        XMLStructure structure = structures.get(0);
        // only dom currently supported
        DOMStructure domStruc = (DOMStructure) structure;
        return domStruc;
    }

    protected List<XMLObject> getReferencedSameDocumentObjects(List<Reference> relevantReferences, List<XMLObject> relevantObjects) {
        List<XMLObject> referencedObjects = new ArrayList<XMLObject>(1);

        for (Reference ref : relevantReferences) {
            String refUri = getSameDocumentReferenceUri(ref);
            if (refUri == null) {
                continue;
            }
            XMLObject referencedOb = getReferencedObject(relevantObjects, refUri);
            if (referencedOb != null) {
                referencedObjects.add(referencedOb);
                continue;
            }
            // content could also be indirectly referenced via manifest
            addManifestReferencedObjects(relevantObjects, referencedObjects, refUri);
        }
        return referencedObjects;
    }

    @SuppressWarnings("unchecked")
    protected void addManifestReferencedObjects(List<XMLObject> allObjects, List<XMLObject> referencedObjects, String manifestId) {
        Manifest manifest = getReferencedManifest(allObjects, manifestId);
        if (manifest == null) {
            return;
        }
        for (Reference manifestRef : (List<Reference>) manifest.getReferences()) {
            String manifestRefUri = getSameDocumentReferenceUri(manifestRef);
            if (manifestRefUri == null) {
                continue;
            }
            XMLObject manifestReferencedOb = getReferencedObject(allObjects, manifestRefUri);
            if (manifestReferencedOb != null) {
                referencedObjects.add(manifestReferencedOb);
            }
        }
    }

    protected String getSameDocumentReferenceUri(Reference ref) {
        String refUri = ref.getURI();
        if (refUri == null) {
            LOG.warn("Ignoring reference {} which has no URI", ref);
            return null;
        }
        if (!refUri.startsWith("#")) {
            LOG.warn("Ignoring non-same document reference {}", refUri);
            return null;
        }
        return refUri.substring(1);
    }

    protected Manifest getReferencedManifest(List<XMLObject> objects, String id) {
        for (XMLObject xo : objects) {
            @SuppressWarnings("unchecked")
            List<XMLStructure> content = xo.getContent();
            for (XMLStructure xs : content) {
                if (xs instanceof Manifest) {
                    Manifest man = (Manifest) xs;
                    if (id.equals(man.getId())) {
                        return man;
                    }
                }
            }
        }
        return null;
    }

    protected XMLObject getReferencedObject(List<XMLObject> objects, String id) {
        for (XMLObject ob : objects) {
            if (id.equals(ob.getId())) {
                return ob;
            }
        }
        return null;
    }
}
TOP

Related Classes of org.apache.camel.component.xmlsecurity.api.DefaultXmlSignature2Message

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.