Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
| /** | |
| * 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.IOException; | |
| import java.io.InputStream; | |
| import java.io.OutputStream; | |
| import java.io.UnsupportedEncodingException; | |
| import java.util.ArrayList; | |
| import java.util.Iterator; | |
| import java.util.LinkedList; | |
| import java.util.List; | |
| import java.util.Map; | |
| import javax.xml.XMLConstants; | |
| import javax.xml.crypto.AlgorithmMethod; | |
| import javax.xml.crypto.dom.DOMStructure; | |
| import javax.xml.crypto.dsig.Transform; | |
| import javax.xml.crypto.dsig.spec.ExcC14NParameterSpec; | |
| import javax.xml.crypto.dsig.spec.XPathFilter2ParameterSpec; | |
| import javax.xml.crypto.dsig.spec.XPathFilterParameterSpec; | |
| import javax.xml.crypto.dsig.spec.XPathType; | |
| import javax.xml.crypto.dsig.spec.XSLTTransformParameterSpec; | |
| import javax.xml.namespace.NamespaceContext; | |
| import javax.xml.parsers.DocumentBuilder; | |
| import javax.xml.parsers.DocumentBuilderFactory; | |
| import javax.xml.parsers.ParserConfigurationException; | |
| import javax.xml.validation.Schema; | |
| import javax.xml.xpath.XPath; | |
| import javax.xml.xpath.XPathExpression; | |
| import javax.xml.xpath.XPathExpressionException; | |
| import javax.xml.xpath.XPathFactory; | |
| import org.w3c.dom.Document; | |
| import org.w3c.dom.Node; | |
| import org.w3c.dom.NodeList; | |
| import org.w3c.dom.bootstrap.DOMImplementationRegistry; | |
| import org.w3c.dom.ls.DOMImplementationLS; | |
| import org.w3c.dom.ls.LSOutput; | |
| import org.w3c.dom.ls.LSSerializer; | |
| import org.xml.sax.SAXException; | |
| import org.apache.camel.util.IOHelper; | |
| /** | |
| * Helps to construct the transformations and the canonicalization methods for | |
| * the XML Signature generator. | |
| */ | |
| public final class XmlSignatureHelper { | |
| private XmlSignatureHelper() { | |
| // Helper class | |
| } | |
| /** | |
| * Returns a configuration for a canonicalization algorithm. | |
| * | |
| * @param algorithm | |
| * algorithm URI | |
| * @return canonicalization | |
| * @throws IllegalArgumentException | |
| * if <tt>algorithm</tt> is <code>null</code> | |
| */ | |
| public static AlgorithmMethod getCanonicalizationMethod(String algorithm) { | |
| return getCanonicalizationMethod(algorithm, null); | |
| } | |
| /** | |
| * Returns a configuration for a canonicalization algorithm. | |
| * | |
| * @param algorithm | |
| * algorithm URI | |
| * @param inclusiveNamespacePrefixes | |
| * namespace prefixes which should be treated like in the | |
| * inclusive canonicalization, only relevant if the algorithm is | |
| * exclusive | |
| * @return canonicalization | |
| * @throws IllegalArgumentException | |
| * if <tt>algorithm</tt> is <code>null</code> | |
| */ | |
| public static AlgorithmMethod getCanonicalizationMethod(String algorithm, List<String> inclusiveNamespacePrefixes) { | |
| if (algorithm == null) { | |
| throw new IllegalArgumentException("algorithm is null"); | |
| } | |
| XmlSignatureTransform canonicalizationMethod = new XmlSignatureTransform(algorithm); | |
| if (inclusiveNamespacePrefixes != null) { | |
| ExcC14NParameterSpec parameters = new ExcC14NParameterSpec(inclusiveNamespacePrefixes); | |
| canonicalizationMethod.setParameterSpec(parameters); | |
| } | |
| return canonicalizationMethod; | |
| } | |
| public static AlgorithmMethod getEnvelopedTransform() { | |
| return new XmlSignatureTransform(Transform.ENVELOPED); | |
| } | |
| /** | |
| * Returns a configuration for a base64 transformation. | |
| * | |
| * @return Base64 transformation | |
| */ | |
| public static AlgorithmMethod getBase64Transform() { | |
| return new XmlSignatureTransform(Transform.BASE64); | |
| } | |
| /** | |
| * Returns a configuration for an XPATH transformation. | |
| * | |
| * @param xpath | |
| * XPATH expression | |
| * @return XPATH transformation | |
| * @throws IllegalArgumentException | |
| * if <tt>xpath</tt> is <code>null</code> | |
| */ | |
| public static AlgorithmMethod getXPathTransform(String xpath) { | |
| return getXPathTransform(xpath, null); | |
| } | |
| /** | |
| * Returns a configuration for an XPATH transformation which needs a | |
| * namespace map. | |
| * | |
| * @param xpath | |
| * XPATH expression | |
| * @param namespaceMap | |
| * namespace map, key is the prefix, value is the namespace, can | |
| * be <code>null</code> | |
| * @throws IllegalArgumentException | |
| * if <tt>xpath</tt> is <code>null</code> | |
| * @return XPATH transformation | |
| */ | |
| public static AlgorithmMethod getXPathTransform(String xpath, Map<String, String> namespaceMap) { | |
| if (xpath == null) { | |
| throw new IllegalArgumentException("xpath is null"); | |
| } | |
| XmlSignatureTransform transformXPath = new XmlSignatureTransform(); | |
| transformXPath.setAlgorithm(Transform.XPATH); | |
| XPathFilterParameterSpec params = getXpathFilter(xpath, namespaceMap); | |
| transformXPath.setParameterSpec(params); | |
| return transformXPath; | |
| } | |
| public static XPathFilterParameterSpec getXpathFilter(String xpath, Map<String, String> namespaceMap) { | |
| XPathFilterParameterSpec params = namespaceMap == null ? new XPathFilterParameterSpec(xpath) : new XPathFilterParameterSpec(xpath, | |
| namespaceMap); | |
| return params; | |
| } | |
| public static XPathFilterParameterSpec getXpathFilter(String xpath) { | |
| return getXpathFilter(xpath, null); | |
| } | |
| @SuppressWarnings("unchecked") | |
| public static XPathExpression getXPathExpression(XPathFilterParameterSpec xpathFilter) throws XPathExpressionException { | |
| XPathFactory factory = XPathFactory.newInstance(); | |
| XPath xpath = factory.newXPath(); | |
| if (xpathFilter.getNamespaceMap() != null) { | |
| xpath.setNamespaceContext(new XPathNamespaceContext(xpathFilter.getNamespaceMap())); | |
| } | |
| return xpath.compile(xpathFilter.getXPath()); | |
| } | |
| private static class XPathNamespaceContext implements NamespaceContext { | |
| private final Map<String, String> prefix2Namespace; | |
| XPathNamespaceContext(Map<String, String> prefix2Namespace) { | |
| this.prefix2Namespace = prefix2Namespace; | |
| } | |
| public String getNamespaceURI(String prefix) { | |
| if (prefix == null) { | |
| throw new NullPointerException("Null prefix"); | |
| } | |
| if ("xml".equals(prefix)) { | |
| return XMLConstants.XML_NS_URI; | |
| } | |
| String ns = prefix2Namespace.get(prefix); | |
| if (ns != null) { | |
| return ns; | |
| } | |
| return XMLConstants.NULL_NS_URI; | |
| } | |
| // This method isn't necessary for XPath processing. | |
| public String getPrefix(String uri) { | |
| throw new UnsupportedOperationException(); | |
| } | |
| // This method isn't necessary for XPath processing either. | |
| @SuppressWarnings("rawtypes") | |
| public Iterator getPrefixes(String uri) { | |
| throw new UnsupportedOperationException(); | |
| } | |
| } | |
| /** | |
| * Returns a configuration for an XPATH2 transformation. | |
| * | |
| * @param xpath | |
| * XPATH expression | |
| * @param filter | |
| * possible values are "intersect", "subtract", "union" | |
| * @throws IllegalArgumentException | |
| * if <tt>xpath</tt> or <tt>filter</tt> is <code>null</code>, or | |
| * is neither "intersect", nor "subtract", nor "union" | |
| * @return XPATH transformation | |
| */ | |
| public static AlgorithmMethod getXPath2Transform(String xpath, String filter) { | |
| return getXPath2Transform(xpath, filter, null); | |
| } | |
| /** | |
| * Returns a configuration for an XPATH2 transformation which consists of | |
| * several XPATH expressions. | |
| * | |
| * @param xpathAndFilterList | |
| * list of XPATH expressions with their filters | |
| * @param namespaceMap | |
| * namespace map, key is the prefix, value is the namespace, can | |
| * be <code>null</code> | |
| * @throws IllegalArgumentException | |
| * if <tt>xpathAndFilterList</tt> is <code>null</code> or empty, | |
| * or the specified filter values are neither "intersect", nor | |
| * "subtract", nor "union" | |
| * @return XPATH transformation | |
| */ | |
| public static AlgorithmMethod getXPath2Transform(String xpath, String filter, Map<String, String> namespaceMap) { | |
| XPathAndFilter xpathAndFilter = new XPathAndFilter(); | |
| xpathAndFilter.setXpath(xpath); | |
| xpathAndFilter.setFilter(filter); | |
| List<XPathAndFilter> list = new ArrayList<>(1); | |
| list.add(xpathAndFilter); | |
| return getXPath2Transform(list, namespaceMap); | |
| } | |
| /** | |
| * Returns a configuration for an XPATH2 transformation which consists of | |
| * several XPATH expressions. | |
| * | |
| * @param xpathAndFilterList | |
| * list of XPATH expressions with their filters | |
| * @param namespaceMap | |
| * namespace map, key is the prefix, value is the namespace, can | |
| * be <code>null</code> | |
| * @throws IllegalArgumentException | |
| * if <tt>xpathAndFilterList</tt> is <code>null</code> or empty, | |
| * or the specified filter values are neither "intersect", nor | |
| * "subtract", nor "union" | |
| * @return XPATH transformation | |
| */ | |
| public static AlgorithmMethod getXPath2Transform(List<XPathAndFilter> xpathAndFilterList, Map<String, String> namespaceMap) { | |
| if (xpathAndFilterList == null) { | |
| throw new IllegalArgumentException("xpathAndFilterList is null"); | |
| } | |
| if (xpathAndFilterList.isEmpty()) { | |
| throw new IllegalArgumentException("XPath and filter list is empty"); | |
| } | |
| List<XPathType> list = getXPathTypeList(xpathAndFilterList, namespaceMap); | |
| XmlSignatureTransform transformXPath = new XmlSignatureTransform(Transform.XPATH2); | |
| transformXPath.setParameterSpec(new XPathFilter2ParameterSpec(list)); | |
| return transformXPath; | |
| } | |
| private static List<XPathType> getXPathTypeList(List<XPathAndFilter> xpathAndFilterList, Map<String, String> namespaceMap) { | |
| List<XPathType> list = new ArrayList<>(xpathAndFilterList.size()); | |
| for (XPathAndFilter xpathAndFilter : xpathAndFilterList) { | |
| XPathType.Filter xpathFilter; | |
| if (XPathType.Filter.INTERSECT.toString().equals(xpathAndFilter.getFilter())) { | |
| xpathFilter = XPathType.Filter.INTERSECT; | |
| } else if (XPathType.Filter.SUBTRACT.toString().equals(xpathAndFilter.getFilter())) { | |
| xpathFilter = XPathType.Filter.SUBTRACT; | |
| } else if (XPathType.Filter.UNION.toString().equals(xpathAndFilter.getFilter())) { | |
| xpathFilter = XPathType.Filter.UNION; | |
| } else { | |
| throw new IllegalStateException(String.format("XPATH %s has a filter %s not supported", xpathAndFilter.getXpath(), | |
| xpathAndFilter.getFilter())); | |
| } | |
| XPathType xpathtype = namespaceMap == null ? new XPathType(xpathAndFilter.getXpath(), xpathFilter) : new XPathType( | |
| xpathAndFilter.getXpath(), xpathFilter, namespaceMap); | |
| list.add(xpathtype); | |
| } | |
| return list; | |
| } | |
| /** | |
| * Returns a configuration for an XPATH2 transformation which consists of | |
| * several XPATH expressions. | |
| * | |
| * @param xpathAndFilterList | |
| * list of XPATH expressions with their filters | |
| * @throws IllegalArgumentException | |
| * if <tt>xpathAndFilterList</tt> is <code>null</code> or empty, | |
| * or the specified filte values are neither "intersect", nor | |
| * "subtract", nor "union" | |
| * @return XPATH transformation | |
| */ | |
| public static AlgorithmMethod getXPath2Transform(List<XPathAndFilter> xpathAndFilterList) { | |
| return getXPath2Transform(xpathAndFilterList, null); | |
| } | |
| /** | |
| * Returns a configuration for an XSL transformation. | |
| * | |
| * @param path | |
| * path to the XSL file in the classpath | |
| * @return XSL transform | |
| * @throws IllegalArgumentException | |
| * if <tt>path</tt> is <code>null</code> | |
| * @throws IllegalStateException | |
| * if the XSL file cannot be found | |
| * @throws Exception | |
| * if an error during the reading of the XSL file occurs | |
| */ | |
| public static AlgorithmMethod getXslTransform(String path) throws Exception { //NOPMD | |
| InputStream is = readXslTransform(path); | |
| if (is == null) { | |
| throw new IllegalStateException(String.format("XSL file %s not found", path)); | |
| } | |
| try { | |
| return getXslTranform(is); | |
| } finally { | |
| IOHelper.close(is); | |
| } | |
| } | |
| /** | |
| * Returns a configuration for an XSL transformation. | |
| * | |
| * @param is | |
| * input stream of the XSL | |
| * @return XSL transform | |
| * @throws IllegalArgumentException | |
| * if <tt>is</tt> is <code>null</code> | |
| * @throws Exception | |
| * if an error during the reading of the XSL file occurs | |
| */ | |
| public static AlgorithmMethod getXslTranform(InputStream is) throws SAXException, IOException, ParserConfigurationException { | |
| if (is == null) { | |
| throw new IllegalArgumentException("is must not be null"); | |
| } | |
| Document doc = parseInput(is); | |
| DOMStructure stylesheet = new DOMStructure(doc.getDocumentElement()); | |
| XSLTTransformParameterSpec spec = new XSLTTransformParameterSpec(stylesheet); | |
| XmlSignatureTransform transformXslt = new XmlSignatureTransform(); | |
| transformXslt.setAlgorithm(Transform.XSLT); | |
| transformXslt.setParameterSpec(spec); | |
| return transformXslt; | |
| } | |
| protected static InputStream readXslTransform(String path) throws Exception { //NOPMD | |
| if (path == null) { | |
| throw new IllegalArgumentException("path is null"); | |
| } | |
| return XmlSignatureHelper.class.getResourceAsStream(path); | |
| } | |
| public static List<AlgorithmMethod> getTransforms(List<AlgorithmMethod> list) { | |
| return list; | |
| } | |
| private static Document parseInput(InputStream is) throws SAXException, IOException, ParserConfigurationException { | |
| return newDocumentBuilder(Boolean.TRUE).parse(is); | |
| } | |
| public static List<Node> getTextAndElementChildren(Node node) { | |
| List<Node> result = new LinkedList<>(); | |
| NodeList children = node.getChildNodes(); | |
| if (children == null) { | |
| return result; | |
| } | |
| for (int i = 0; i < children.getLength(); i++) { | |
| Node child = children.item(i); | |
| if (Node.ELEMENT_NODE == child.getNodeType() || Node.TEXT_NODE == child.getNodeType()) { | |
| result.add(child); | |
| } | |
| } | |
| return result; | |
| } | |
| public static DocumentBuilder newDocumentBuilder(Boolean disallowDoctypeDecl) throws ParserConfigurationException { | |
| return newDocumentBuilder(disallowDoctypeDecl, null); | |
| } | |
| public static DocumentBuilder newDocumentBuilder(Boolean disallowDoctypeDecl, Schema schema) throws ParserConfigurationException { | |
| DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); | |
| dbf.setNamespaceAware(true); | |
| dbf.setValidating(false); | |
| // avoid external entity attacks | |
| dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); | |
| dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); | |
| boolean isDissalowDoctypeDecl = disallowDoctypeDecl == null ? true : disallowDoctypeDecl; | |
| dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", isDissalowDoctypeDecl); | |
| // avoid overflow attacks | |
| dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); | |
| if (schema != null) { | |
| dbf.setSchema(schema); | |
| } | |
| return dbf.newDocumentBuilder(); | |
| } | |
| public static void transformToOutputStream(Node node, OutputStream os, boolean omitXmlDeclaration, String encoding) throws Exception { //NOPMD | |
| if (node.getNodeType() == Node.TEXT_NODE) { | |
| byte[] bytes = tranformTextNodeToByteArray(node, encoding); | |
| os.write(bytes); | |
| } else { | |
| transformNonTextNodeToOutputStream(node, os, omitXmlDeclaration, encoding); | |
| } | |
| } | |
| /** | |
| * Use {@link #transformToOutputStream(Node, OutputStream, boolean, String)} | |
| * instead. | |
| */ | |
| @Deprecated | |
| public static void transformToOutputStream(Node node, OutputStream os, boolean omitXmlDeclaration) throws Exception { //NOPMD | |
| if (node.getNodeType() == Node.TEXT_NODE) { | |
| byte[] bytes = tranformTextNodeToByteArray(node); | |
| os.write(bytes); | |
| } else { | |
| transformNonTextNodeToOutputStream(node, os, omitXmlDeclaration); | |
| } | |
| } | |
| /** | |
| * Use | |
| * {@link #transformNonTextNodeToOutputStream(Node, OutputStream, boolean, String)} | |
| * instead. | |
| */ | |
| @Deprecated | |
| public static void transformNonTextNodeToOutputStream(Node node, OutputStream os, boolean omitXmlDeclaration) throws Exception { //NOPMD | |
| transformNonTextNodeToOutputStream(node, os, omitXmlDeclaration, null); | |
| } | |
| /** | |
| * Serializes a node using a certain character encoding. | |
| * | |
| * @param node | |
| * DOM node to serialize | |
| * @param os | |
| * output stream, to which the node is serialized | |
| * @param omitXmlDeclaration | |
| * indicator whether to omit the XML declaration or not | |
| * @param encoding | |
| * character encoding, can be <code>null</code>, if | |
| * <code>null</code> then "UTF-8" is used | |
| * @throws Exception | |
| */ | |
| public static void transformNonTextNodeToOutputStream(Node node, OutputStream os, boolean omitXmlDeclaration, String encoding) | |
| throws Exception { //NOPMD | |
| // previously we used javax.xml.transform.Transformer, however the JDK xalan implementation did not work correctly with a specified encoding | |
| // therefore we switched to DOMImplementationLS | |
| if (encoding == null) { | |
| encoding = "UTF-8"; | |
| } | |
| DOMImplementationRegistry domImplementationRegistry = DOMImplementationRegistry.newInstance(); | |
| DOMImplementationLS domImplementationLS = (DOMImplementationLS) domImplementationRegistry.getDOMImplementation("LS"); | |
| LSOutput lsOutput = domImplementationLS.createLSOutput(); | |
| lsOutput.setEncoding(encoding); | |
| lsOutput.setByteStream(os); | |
| LSSerializer lss = domImplementationLS.createLSSerializer(); | |
| lss.getDomConfig().setParameter("xml-declaration", !omitXmlDeclaration); | |
| lss.write(node, lsOutput); | |
| } | |
| /** use {@link #tranformTextNodeToByteArray(Node, String)} instead. */ | |
| @Deprecated | |
| public static byte[] tranformTextNodeToByteArray(Node node) { | |
| return tranformTextNodeToByteArray(node, null); | |
| } | |
| /** | |
| * Trannsforms a text node to byte array using a certain character encoding. | |
| * | |
| * @param node | |
| * text node | |
| * @param encoding | |
| * character encoding, can be <code>null</code>, if | |
| * <code>null</code> then UTF-8 is used | |
| * @return byte array, <code>null</code> if the node has not text content | |
| * @throws IllegalStateException | |
| * if the encoding is not supported | |
| */ | |
| public static byte[] tranformTextNodeToByteArray(Node node, String encoding) { | |
| if (encoding == null) { | |
| encoding = "UTF-8"; | |
| } | |
| String text = node.getTextContent(); | |
| if (text != null) { | |
| try { | |
| return text.getBytes(encoding); | |
| } catch (UnsupportedEncodingException e) { | |
| throw new IllegalStateException(e); | |
| } | |
| } else { | |
| return null; | |
| } | |
| } | |
| public static Document getDocument(Node node) { | |
| if (node.getNodeType() == Node.DOCUMENT_NODE) { | |
| return (Document) node; | |
| } | |
| return node.getOwnerDocument(); | |
| } | |
| public static class XPathAndFilter { | |
| private String xpath; | |
| private String filter; | |
| public XPathAndFilter(String xpath, String filter) { | |
| this.xpath = xpath; | |
| this.filter = filter; | |
| } | |
| public XPathAndFilter() { | |
| } | |
| public String getXpath() { | |
| return xpath; | |
| } | |
| public void setXpath(String xpath) { | |
| this.xpath = xpath; | |
| } | |
| public String getFilter() { | |
| return filter; | |
| } | |
| public void setFilter(String filter) { | |
| this.filter = filter; | |
| } | |
| } | |
| } |