Package org.apache.camel.component.xmlsecurity

Source Code of org.apache.camel.component.xmlsecurity.XmlSignatureTest

/**
* 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;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.Key;
import java.security.KeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.xml.crypto.AlgorithmMethod;
import javax.xml.crypto.KeySelector;
import javax.xml.crypto.KeySelectorException;
import javax.xml.crypto.KeySelectorResult;
import javax.xml.crypto.URIDereferencer;
import javax.xml.crypto.XMLCryptoContext;
import javax.xml.crypto.XMLStructure;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.KeyValue;
import javax.xml.crypto.dsig.spec.XPathFilterParameterSpec;
import javax.xml.crypto.dsig.spec.XPathType;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.component.xmlsecurity.api.KeyAccessor;
import org.apache.camel.component.xmlsecurity.api.ValidationFailedHandler;
import org.apache.camel.component.xmlsecurity.api.XmlSignature2Message;
import org.apache.camel.component.xmlsecurity.api.XmlSignatureChecker;
import org.apache.camel.component.xmlsecurity.api.XmlSignatureConstants;
import org.apache.camel.component.xmlsecurity.api.XmlSignatureException;
import org.apache.camel.component.xmlsecurity.api.XmlSignatureFormatException;
import org.apache.camel.component.xmlsecurity.api.XmlSignatureHelper;
import org.apache.camel.component.xmlsecurity.api.XmlSignatureHelper.XPathAndFilter;
import org.apache.camel.component.xmlsecurity.api.XmlSignatureInvalidContentHashException;
import org.apache.camel.component.xmlsecurity.api.XmlSignatureInvalidException;
import org.apache.camel.component.xmlsecurity.api.XmlSignatureInvalidKeyException;
import org.apache.camel.component.xmlsecurity.api.XmlSignatureInvalidValueException;
import org.apache.camel.component.xmlsecurity.api.XmlSignatureProperties;
import org.apache.camel.component.xmlsecurity.processor.XmlSignatureConfiguration;
import org.apache.camel.component.xmlsecurity.util.EnvelopingXmlSignatureChecker;
import org.apache.camel.component.xmlsecurity.util.SameDocumentUriDereferencer;
import org.apache.camel.component.xmlsecurity.util.TestKeystore;
import org.apache.camel.component.xmlsecurity.util.TimestampProperty;
import org.apache.camel.component.xmlsecurity.util.ValidationFailedHandlerIgnoreManifestFailures;
import org.apache.camel.component.xmlsecurity.util.XmlSignature2Message2MessageWithTimestampProperty;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.impl.JndiRegistry;
import org.apache.camel.processor.validation.SchemaValidationException;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.junit.Before;
import org.junit.Test;

public class XmlSignatureTest extends CamelTestSupport {

    private static String payload = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
            + "<root xmlns=\"http://test/test\"><test>Test Message</test></root>";
    private KeyPair keyPair;

    @Override
    protected JndiRegistry createRegistry() throws Exception {
        JndiRegistry registry = super.createRegistry();

        registry.bind("accessor", getKeyAccessor(keyPair.getPrivate()));
        registry.bind("canonicalizationMethod1", getCanonicalizationMethod());
        registry.bind("selector", KeySelector.singletonKeySelector(keyPair.getPublic()));
        registry.bind("selectorKeyValue", getKeyValueKeySelector());
        registry.bind("transformsXPath2", getTransformsXPath2());
        registry.bind("transformsXsltXPath", getTransformsXsltXpath());
        registry.bind("uriDereferencer", getSameDocumentUriDereferencer());
        registry.bind("baseUri", getBaseUri());
        registry.bind("cryptoContextProperties", getCrytoContextProperties());
        registry.bind("keyAccessorDefault", getDefaultKeyAccessor());
        registry.bind("keySelectorDefault", getDefaultKeySelector());
        registry.bind("envelopingSignatureChecker", getEnvelopingXmlSignatureChecker());
        registry.bind("xmlSignature2MessageWithTimestampProperty", getXmlSignature2MessageWithTimestampdProperty());
        registry.bind("validationFailedHandlerIgnoreManifestFailures", getValidationFailedHandlerIgnoreManifestFailures());
        registry.bind("signatureProperties", getSignatureProperties());
        registry.bind("nodesearchxpath", getNodeSerachXPath());
        Map<String, String> namespaceMap = Collections.singletonMap("ns", "http://test");
        List<XPathFilterParameterSpec> xpaths = Collections
                .singletonList(XmlSignatureHelper.getXpathFilter("/ns:root/a/@ID", namespaceMap));
        registry.bind("xpathsToIdAttributes", xpaths);

        registry.bind("parentXpathBean", getParentXPathBean());

        return registry;
    }

    @Override
    protected RouteBuilder[] createRouteBuilders() throws Exception {
        return new RouteBuilder[] {new RouteBuilder() {
            public void configure() throws Exception {
                // START SNIPPET: enveloping XML signature
                onException(XmlSignatureException.class).handled(true).to("mock:exception");
                from("direct:enveloping").to(getSignerEndpointURIEnveloping()).to("mock:signed").to(getVerifierEncpointURIEnveloping())
                        .to("mock:result");
                // END SNIPPET: enveloping XML signature
            }
        }, new RouteBuilder() {
            public void configure() throws Exception {
                // START SNIPPET: enveloping XML signature with plain text
                // message body
                onException(XmlSignatureException.class).handled(true).to("mock:exception");
                from("direct:plaintext").to("xmlsecurity:sign://plaintext?keyAccessor=#accessor&plainText=true&plainTextEncoding=UTF-8")
                        .to("xmlsecurity:verify://plaintext?keySelector=#selector").to("mock:result");
                // END SNIPPET: enveloping XML signature with plain text message
                // body
            }
        }, new RouteBuilder() {
            public void configure() throws Exception {
                // START SNIPPET: enveloped XML signature
                onException(XmlSignatureException.class).handled(true).to("mock:exception");
                from("direct:enveloped").to(getSignerEndpointURIEnveloped()).to("mock:signed").to(getVerifierEndpointURIEnveloped())
                        .to("mock:result");
                // END SNIPPET: enveloped XML signature
            }
        }, new RouteBuilder() {
            public void configure() throws Exception {
                // START SNIPPET: canonicalization
                // we can set the configuration properties explicitly on the
                // endpoint instances.
                context.getEndpoint("xmlsecurity:sign://canonicalization?canonicalizationMethod=#canonicalizationMethod1",
                        XmlSignerEndpoint.class).setKeyAccessor(getKeyAccessor(keyPair.getPrivate()));
                context.getEndpoint("xmlsecurity:sign://canonicalization?canonicalizationMethod=#canonicalizationMethod1",
                        XmlSignerEndpoint.class).setSignatureAlgorithm("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
                context.getEndpoint("xmlsecurity:verify://canonicalization", XmlVerifierEndpoint.class).setKeySelector(
                        KeySelector.singletonKeySelector(keyPair.getPublic()));
                from("direct:canonicalization").to("xmlsecurity:sign://canonicalization?canonicalizationMethod=#canonicalizationMethod1",
                        "xmlsecurity:verify://canonicalization", "mock:result");
                // END SNIPPET: canonicalization
            }
        }, new RouteBuilder() {
            public void configure() throws Exception {
                // START SNIPPET: signature and digest algorithm
                from("direct:signaturedigestalgorithm")
                        .to("xmlsecurity:sign://signaturedigestalgorithm?keyAccessor=#accessor"
                                + "&signatureAlgorithm=http://www.w3.org/2001/04/xmldsig-more#rsa-sha512&digestAlgorithm=http://www.w3.org/2001/04/xmlenc#sha512",
                                "xmlsecurity:verify://signaturedigestalgorithm?keySelector=#selector").to("mock:result");
                // END SNIPPET: signature and digest algorithm
            }
        }, new RouteBuilder() {
            public void configure() throws Exception {
                // START SNIPPET: transforms XPath2
                from("direct:transformsXPath2").to(
                        "xmlsecurity:sign://transformsXPath2?keyAccessor=#accessor&transformMethods=#transformsXPath2",
                        "xmlsecurity:verify://transformsXPath2?keySelector=#selector").to("mock:result");
                // END SNIPPET: transform XPath
            }
        }, new RouteBuilder() {
            public void configure() throws Exception {
                // START SNIPPET: transforms XSLT,XPath
                onException(XmlSignatureException.class).handled(false).to("mock:exception");
                from("direct:transformsXsltXPath").to(
                        "xmlsecurity:sign://transformsXsltXPath?keyAccessor=#accessor&transformMethods=#transformsXsltXPath",
                        "xmlsecurity:verify://transformsXsltXPath?keySelector=#selector").to("mock:result");
                // END SNIPPET: transforms XSLT,XPath
            }
        }, new RouteBuilder() {
            public void configure() throws Exception {
                // START SNIPPET: transforms XSLT,XPath - secure Validation
                // disabled
                from("direct:transformsXsltXPathSecureValDisabled")
                        .to("xmlsecurity:sign://transformsXsltXPathSecureValDisabled?keyAccessor=#accessor&transformMethods=#transformsXsltXPath",
                                "xmlsecurity:verify://transformsXsltXPathSecureValDisabled?keySelector=#selector&secureValidation=false")
                        .to("mock:result");
                // END SNIPPET: transforms XSLT,XPath - secure Validation
                // disabled
            }
        }, new RouteBuilder() {
            public void configure() throws Exception {
                // START SNIPPET: cryptocontextprops
                onException(XmlSignatureException.class).handled(false).to("mock:exception");
                from("direct:cryptocontextprops")
                        .to("xmlsecurity:verify://cryptocontextprops?keySelector=#selectorKeyValue&cryptoContextProperties=#cryptoContextProperties")
                        .to("mock:result");
                // END SNIPPET: cryptocontextprops
            }
        }, new RouteBuilder() {
            public void configure() throws Exception {
                // START SNIPPET: URI dereferencer
                from("direct:uridereferencer")
                        .to("xmlsecurity:sign://uriderferencer?keyAccessor=#accessor&uriDereferencer=#uriDereferencer")
                        .to("xmlsecurity:verify://uridereferencer?keySelector=#selector&uriDereferencer=#uriDereferencer")
                        .to("mock:result");
                // END SNIPPET: URI dereferencer
            }
        }, new RouteBuilder() {
            public void configure() throws Exception {
                // START SNIPPET: keyAccessorKeySelectorDefault
                from("direct:keyAccessorKeySelectorDefault")
                        .to("xmlsecurity:sign://keyAccessorKeySelectorDefault?keyAccessor=#keyAccessorDefault&addKeyInfoReference=true")
                        .to("xmlsecurity:verify://keyAccessorKeySelectorDefault?keySelector=#keySelectorDefault").to("mock:result");
                // END SNIPPET: keyAccessorKeySelectorDefault
            }
        }, new RouteBuilder() {
            public void configure() throws Exception {
                // START SNIPPET: xmlSignatureChecker
                onException(XmlSignatureInvalidException.class).handled(false).to("mock:exception");
                from("direct:xmlSignatureChecker")
                        .to("xmlsecurity:verify://xmlSignatureChecker?keySelector=#selectorKeyValue&xmlSignatureChecker=#envelopingSignatureChecker")
                        .to("mock:result");
                // END SNIPPET: xmlSignatureChecker
            }
        }, new RouteBuilder() {
            public void configure() throws Exception { //
                // START SNIPPET: properties
                from("direct:props")
                        .to("xmlsecurity:sign://properties?keyAccessor=#accessor&properties=#signatureProperties")
                        .to("xmlsecurity:verify://properties?keySelector=#selector&xmlSignature2Message=#xmlSignature2MessageWithTimestampProperty")
                        .to("mock:result");
                // END SNIPPET: properties
            }
        }, new RouteBuilder() {
            public void configure() throws Exception {
                // START SNIPPET: verify output node search element name
                onException(XmlSignatureException.class).handled(true).to("mock:exception");
                from("direct:outputnodesearchelementname").to(
                        "xmlsecurity:verify://outputnodesearchelementname?keySelector=#selectorKeyValue"
                                + "&outputNodeSearchType=ElementName&outputNodeSearch={http://test/test}root&removeSignatureElements=true")
                        .to("mock:result");
                // END SNIPPET: verify output node search element name
            }
        }, new RouteBuilder() {
            public void configure() throws Exception {
                // START SNIPPET: verify output node search xpath
                onException(XmlSignatureException.class).handled(true).to("mock:exception");
                from("direct:outputnodesearchxpath")
                        .to("xmlsecurity:verify://outputnodesearchxpath?keySelector=#selectorKeyValue&outputNodeSearchType=XPath&outputNodeSearch=#nodesearchxpath&removeSignatureElements=true")
                        .to("mock:result");
                // END SNIPPET: verify output node search xpath
            }
        }, new RouteBuilder() {
            public void configure() throws Exception {
                // START SNIPPET: validationFailedHandler
                from("direct:validationFailedHandler")
                        .to("xmlsecurity:verify://validationFailedHandler?keySelector=#selectorKeyValue&validationFailedHandler=validationFailedHandlerIgnoreManifestFailures")
                        .to("mock:result");
                // END SNIPPET: validationFailedHandler
            }
        }, new RouteBuilder() {
            public void configure() throws Exception {
                // START SNIPPET: further parameters
                from("direct:furtherparams")
                        .to("xmlsecurity:sign://furtherparams?keyAccessor=#accessor&prefixForXmlSignatureNamespace=digsig&disallowDoctypeDecl=false")
                        .to("xmlsecurity:verify://bfurtherparams?keySelector=#selector&disallowDoctypeDecl=false").to("mock:result");
                // END SNIPPET: further parameters
            }
        }, new RouteBuilder() {
            public void configure() throws Exception {
                // START SNIPPET: signer invalid keyexception
                onException(XmlSignatureInvalidKeyException.class).handled(true).to("mock:exception");
                from("direct:signexceptioninvalidkey").to(
                        "xmlsecurity:sign://signexceptioninvalidkey?signatureAlgorithm=http://www.w3.org/2001/04/xmldsig-more#rsa-sha512")
                        .to("mock:result");
                // END SNIPPET: signer invalid keyexception
            }
        }, new RouteBuilder() {
            public void configure() throws Exception {
                // START SNIPPET: signer exceptions
                onException(XmlSignatureException.class).handled(true).to("mock:exception");
                from("direct:signexceptions")
                        .to("xmlsecurity:sign://signexceptions?keyAccessor=#accessor&signatureAlgorithm=http://www.w3.org/2001/04/xmldsig-more#rsa-sha512")
                        .to("mock:result");
                // END SNIPPET: signer exceptions
            }
        }, new RouteBuilder() {
            public void configure() throws Exception {
                onException(XmlSignatureException.class).handled(true).to("mock:exception");
                from("direct:noSuchAlgorithmException")
                        .to("xmlsecurity:sign://noSuchAlgorithmException?keyAccessor=#accessor&signatureAlgorithm=wrongalgorithm&digestAlgorithm=http://www.w3.org/2001/04/xmlenc#sha512")
                        .to("mock:result");
            }
        }, new RouteBuilder() {
            public void configure() throws Exception {
                onException(XmlSignatureException.class).handled(false).to("mock:exception");
                from("direct:verifyexceptions").to("xmlsecurity:verify://verifyexceptions?keySelector=#selector").to("mock:result");
            }
        }, new RouteBuilder() {
            public void configure() throws Exception {
                onException(XmlSignatureException.class).handled(false).to("mock:exception");
                from("direct:verifyInvalidKeyException").to("xmlsecurity:verify://verifyInvalidKeyException?keySelector=#selector").to(
                        "mock:result");
            }
        }, new RouteBuilder() {
            public void configure() throws Exception {
                onException(XmlSignatureException.class).handled(false).to("mock:exception");
                from("direct:invalidhash").to(
                        "xmlsecurity:verify://invalidhash?keySelector=#selectorKeyValue&baseUri=#baseUri&secureValidation=false").to(
                        "mock:result");
            }
        }, createDetachedRoute(), createRouteForEnvelopedWithParentXpath() };
    }

    RouteBuilder createDetachedRoute() {
        return new RouteBuilder() {
            public void configure() throws Exception {
                // START SNIPPET: detached XML signature
                onException(Exception.class).handled(false).to("mock:exception");
                from("direct:detached")
                        .to("xmlsecurity:sign://detached?keyAccessor=#keyAccessorDefault&xpathsToIdAttributes=#xpathsToIdAttributes&"//
                                + "schemaResourceUri=org/apache/camel/component/xmlsecurity/Test.xsd&signatureId=&clearHeaders=false")
                        .to("mock:result")
                        .to("xmlsecurity:verify://detached?keySelector=#keySelectorDefault&schemaResourceUri=org/apache/camel/component/xmlsecurity/Test.xsd")
                        .to("mock:verified");
                // END SNIPPET: detached XML signature
            }
        };
    }

    private RouteBuilder createRouteForEnvelopedWithParentXpath() {
        return new RouteBuilder() {
            public void configure() throws Exception {
                // START SNIPPET: enveloped XML signature with parent XPath
                onException(XmlSignatureException.class).handled(false).to("mock:exception");
                from("direct:envelopedParentXpath").to("xmlsecurity:sign://enveloped?keyAccessor=#accessor&parentXpath=#parentXpathBean")
                        .to("mock:signed").to(getVerifierEndpointURIEnveloped()).to("mock:result");
                // END SNIPPET: enveloped XML signature with parent XPath
            }
        };
    }

    @Test
    public void testEnvelopingSignature() throws Exception {
        setupMock();
        sendBody("direct:enveloping", payload);
        assertMockEndpointsSatisfied();
    }

    @Test
    public void testEnvelopingSignatureWithPlainText() throws Exception {
        String text = "plain test text";
        setupMock(text);
        sendBody("direct:plaintext", text);
        assertMockEndpointsSatisfied();
    }

    @Test
    public void testEnvelopingSignatureWithPlainTextSetByHeaders() throws Exception {
        String text = "plain test text";
        setupMock(text);
        Map<String, Object> headers = new TreeMap<String, Object>();
        headers.put(XmlSignatureConstants.HEADER_MESSAGE_IS_PLAIN_TEXT, Boolean.TRUE);
        headers.put(XmlSignatureConstants.HEADER_PLAIN_TEXT_ENCODING, "UTF-8");
        sendBody("direct:enveloping", text, headers);
        assertMockEndpointsSatisfied();
    }

    @Test
    public void testExceptionSignatureForPlainTextWithWrongEncoding() throws Exception {
        String text = "plain test text";
        MockEndpoint mock = setupExceptionMock();
        Map<String, Object> headers = new TreeMap<String, Object>();
        headers.put(XmlSignatureConstants.HEADER_MESSAGE_IS_PLAIN_TEXT, Boolean.TRUE);
        headers.put(XmlSignatureConstants.HEADER_PLAIN_TEXT_ENCODING, "wrongEncoding");
        sendBody("direct:enveloping", text, headers);
        assertMockEndpointsSatisfied();
        checkThrownException(mock, XmlSignatureException.class, UnsupportedEncodingException.class);
    }

    @Test
    public void testEnvelopedSignature() throws Exception {
        setupMock(payload);
        sendBody("direct:enveloped", payload);
        assertMockEndpointsSatisfied();
    }

    @Test
    public void testExceptionEnvelopedSignatureWithWrongParent() throws Exception {
        // payload root element renamed to a -> parent name in route definition
        // does not fit
        String payload = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><a xmlns=\"http://test/test\"><test>Test Message</test></a>";

        MockEndpoint mock = setupExceptionMock();
        sendBody("direct:enveloped", payload);
        assertMockEndpointsSatisfied();
        checkThrownException(mock, XmlSignatureFormatException.class, null);
    }

    @Test
    public void testExceptionEnvelopedSignatureWithPlainTextPayload() throws Exception {
        // payload root element renamed to a -> parent name in route definition
        // does not fit
        String payload = "plain text Message";
        Map<String, Object> headers = new HashMap<String, Object>(1);
        headers.put(XmlSignatureConstants.HEADER_MESSAGE_IS_PLAIN_TEXT, Boolean.TRUE);
        MockEndpoint mock = setupExceptionMock();
        sendBody("direct:enveloped", payload, headers);
        assertMockEndpointsSatisfied();
        checkThrownException(mock, XmlSignatureFormatException.class, null);
    }

    /**
     * The parameter can also be configured via
     * {@link XmlSignatureConfiguration#setOmitXmlDeclaration(Boolean)}
     */
    @Test
    public void testOmitXmlDeclarationViaHeader() throws Exception {
        String payloadOut = "<root xmlns=\"http://test/test\"><test>Test Message</test></root>";
        setupMock(payloadOut);
        Map<String, Object> headers = new TreeMap<String, Object>();
        headers.put(XmlSignatureConstants.HEADER_OMIT_XML_DECLARATION, Boolean.TRUE);
        InputStream payload = XmlSignatureTest.class
                .getResourceAsStream("/org/apache/camel/component/xmlsecurity/ExampleEnvelopedXmlSig.xml");
        assertNotNull("Cannot load payload", payload);
        sendBody("direct:outputnodesearchelementname", payload, headers);
        assertMockEndpointsSatisfied();
    }

    @Test
    public void testkeyAccessorKeySelectorDefault() throws Exception {
        setupMock();
        sendBody("direct:keyAccessorKeySelectorDefault", payload);
        assertMockEndpointsSatisfied();
    }

    @Test
    public void testSetCanonicalizationMethodInRouteDefinition() throws Exception {
        setupMock();
        sendBody("direct:canonicalization", payload);
        assertMockEndpointsSatisfied();
    }

    @Test
    public void testSetDigestAlgorithmInRouteDefinition() throws Exception {

        setupMock();
        sendBody("direct:signaturedigestalgorithm", payload);
        assertMockEndpointsSatisfied();
    }

    @Test
    public void testSetTransformMethodXpath2InRouteDefinition() throws Exception {
        // example from http://www.w3.org/TR/2002/REC-xmldsig-filter2-20021108/
        String payload = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
                + "<Document xmlns=\"http://test/test\">                             "
                + "<ToBeSigned>                                                     "
                + "   <!-- comment -->                                              "
                + "   <Data>1</Data>                                                "
                + "   <NotToBeSigned>                                               "
                + "     <ReallyToBeSigned>                                          "
                + "       <!-- comment -->                                          "
                + "       <Data>2</Data>                                            "
                + "     </ReallyToBeSigned>                                         "
                + "   </NotToBeSigned>                                              "
                + " </ToBeSigned>                                                   "
                + " <ToBeSigned>                                                    "
                + "   <Data>3</Data>                                                "
                + "   <NotToBeSigned>                                               "
                + "     <Data>4</Data>                                              "
                + "   </NotToBeSigned>                                              "
                + " </ToBeSigned>                                                   " + "</Document>";

        setupMock(payload);
        sendBody("direct:transformsXPath2", payload);
        assertMockEndpointsSatisfied();
    }

    // Secure Validation is enabled and so this should fail
    @Test
    public void testSetTransformMethodXsltXpathInRouteDefinition() throws Exception {
        // byte[] encoded = Base64.encode("Test Message".getBytes("UTF-8"));
        // String contentBase64 = new String(encoded, "UTF-8");
        // String payload =
        // "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root xmlns=\"http://test/test\"><test></test></root>";
        MockEndpoint mock = setupExceptionMock();
        sendBody("direct:transformsXsltXPath", payload);
        assertMockEndpointsSatisfied();
        checkThrownException(mock, XmlSignatureException.class, null);
    }

    @Test
    public void testSetTransformMethodXsltXpathInRouteDefinitionSecValDisabled() throws Exception {
        setupMock();
        sendBody("direct:transformsXsltXPathSecureValDisabled", payload);
        assertMockEndpointsSatisfied();
    }

    @Test
    public void testProperties() throws Exception {
        setupMock();
        sendBody("direct:props", payload);
        assertMockEndpointsSatisfied();
    }

    @Test
    public void testVerifyOutputNodeSearchElementName() throws Exception {
        setupMock();
        InputStream payload = XmlSignatureTest.class
                .getResourceAsStream("/org/apache/camel/component/xmlsecurity/ExampleEnvelopedXmlSig.xml");
        assertNotNull("Cannot load payload", payload);
        sendBody("direct:outputnodesearchelementname", payload);
        assertMockEndpointsSatisfied();
    }

    @Test
    public void testVerifyExceptionOutputNodeSearchElementNameInvalidFormat1() throws Exception {
        XmlVerifierEndpoint endpoint = context.getEndpoint("xmlsecurity:verify://outputnodesearchelementname?keySelector=#selectorKeyValue"
                + "&outputNodeSearchType=ElementName&outputNodeSearch={http://test/test}root&removeSignatureElements=true",
                XmlVerifierEndpoint.class);
        endpoint.setOutputNodeSearch("{wrongformat"); // closing '}' missing
        MockEndpoint mock = setupExceptionMock();
        InputStream payload = XmlSignatureTest.class
                .getResourceAsStream("/org/apache/camel/component/xmlsecurity/ExampleEnvelopedXmlSig.xml");
        assertNotNull("Cannot load payload", payload);
        sendBody("direct:outputnodesearchelementname", payload);
        assertMockEndpointsSatisfied();
        checkThrownException(mock, XmlSignatureException.class, null);
    }

    @Test
    public void testVerifyExceptionOutputNodeSearchElementNameInvalidFormat2() throws Exception {
        context.getEndpoint(
                "xmlsecurity:verify://outputnodesearchelementname?keySelector=#selectorKeyValue"
                        + "&outputNodeSearchType=ElementName&outputNodeSearch={http://test/test}root&removeSignatureElements=true",
                XmlVerifierEndpoint.class).setOutputNodeSearch("{wrongformat}");
        // local name missing
        MockEndpoint mock = setupExceptionMock();
        InputStream payload = XmlSignatureTest.class
                .getResourceAsStream("/org/apache/camel/component/xmlsecurity/ExampleEnvelopedXmlSig.xml");
        assertNotNull("Cannot load payload", payload);
        sendBody("direct:outputnodesearchelementname", payload);
        assertMockEndpointsSatisfied();
        checkThrownException(mock, XmlSignatureException.class, null);
    }

    @Test
    public void testExceptionVerifyOutputNodeSearchWrongElementName() throws Exception {
        MockEndpoint mock = setupExceptionMock();
        InputStream payload = XmlSignatureTest.class
                .getResourceAsStream("/org/apache/camel/component/xmlsecurity/ExampleEnvelopingDigSig.xml");
        assertNotNull("Cannot load payload", payload);
        sendBody("direct:outputnodesearchelementname", payload);
        assertMockEndpointsSatisfied();
        checkThrownException(mock, XmlSignatureException.class, null);
    }

    @Test
    public void testExceptionVerifyOutputNodeSearchElementNameMoreThanOneOutputElement() throws Exception {
        MockEndpoint mock = setupExceptionMock();
        InputStream payload = XmlSignatureTest.class
                .getResourceAsStream("/org/apache/camel/component/xmlsecurity/ExampleEnvelopingDigSigWithSeveralElementsWithNameRoot.xml");
        assertNotNull("Cannot load payload", payload);
        sendBody("direct:outputnodesearchelementname", payload);
        assertMockEndpointsSatisfied();
        checkThrownException(mock, XmlSignatureException.class, null);
    }

    @Test
    public void testVerifyOutputNodeSearchXPath() throws Exception {
        setupMock();
        InputStream payload = XmlSignatureTest.class
                .getResourceAsStream("/org/apache/camel/component/xmlsecurity/ExampleEnvelopedXmlSig.xml");
        assertNotNull("Cannot load payload", payload);
        sendBody("direct:outputnodesearchxpath", payload);
        assertMockEndpointsSatisfied();
    }

    @Test
    public void testExceptionVerifyOutputNodeSearchXPathWithNoResultNode() throws Exception {
        MockEndpoint mock = setupExceptionMock();
        InputStream payload = XmlSignatureTest.class
                .getResourceAsStream("/org/apache/camel/component/xmlsecurity/ExampleEnvelopingDigSig.xml");
        assertNotNull("Cannot load payload", payload);
        sendBody("direct:outputnodesearchxpath", payload);
        assertMockEndpointsSatisfied();
        checkThrownException(mock, XmlSignatureException.class, null);
    }

    @Test
    public void testExceptionVerifyOutputNodeSearchXPathMoreThanOneOutputElement() throws Exception {
        MockEndpoint mock = setupExceptionMock();
        InputStream payload = XmlSignatureTest.class
                .getResourceAsStream("/org/apache/camel/component/xmlsecurity/ExampleEnvelopingDigSigWithSeveralElementsWithNameRoot.xml");
        assertNotNull("Cannot load payload", payload);
        sendBody("direct:outputnodesearchxpath", payload);
        assertMockEndpointsSatisfied();
        checkThrownException(mock, XmlSignatureException.class, null);
    }

    @Test
    public void testInvalidKeyException() throws Exception {
        MockEndpoint mock = setupExceptionMock();
        // wrong key type
        setUpKeys("DSA", 512);
        context.getEndpoint(
                "xmlsecurity:sign://signexceptioninvalidkey?signatureAlgorithm=http://www.w3.org/2001/04/xmldsig-more#rsa-sha512",
                XmlSignerEndpoint.class).setKeyAccessor(getKeyAccessor(keyPair.getPrivate()));
        sendBody("direct:signexceptioninvalidkey", payload);
        assertMockEndpointsSatisfied();
        checkThrownException(mock, XmlSignatureInvalidKeyException.class, null);
    }

    @Test
    public void testSignatureFormatException() throws Exception {
        MockEndpoint mock = setupExceptionMock();
        sendBody("direct:signexceptions", "wrongFormatedPayload");
        assertMockEndpointsSatisfied();
        checkThrownException(mock, XmlSignatureFormatException.class, null);
    }

    @Test
    public void testNoSuchAlgorithmException() throws Exception {
        MockEndpoint mock = setupExceptionMock();
        sendBody("direct:noSuchAlgorithmException", payload);
        assertMockEndpointsSatisfied();
        checkThrownException(mock, XmlSignatureException.class, NoSuchAlgorithmException.class);
    }

    @Test
    public void testVerifyFormatExceptionNoXml() throws Exception {
        MockEndpoint mock = setupExceptionMock();
        sendBody("direct:verifyexceptions", "wrongFormatedPayload");
        assertMockEndpointsSatisfied();
        checkThrownException(mock, XmlSignatureFormatException.class, null);
    }

    @Test
    public void testVerifyFormatExceptionNoXmlWithoutSignatureElement() throws Exception {
        MockEndpoint mock = setupExceptionMock();
        sendBody("direct:verifyexceptions", "<?xml version=\"1.0\" encoding=\"UTF-8\"?><NoSignature></NoSignature>");
        assertMockEndpointsSatisfied();
        checkThrownException(mock, XmlSignatureFormatException.class, null);
    }

    @Test
    public void testVerifyInvalidContentHashException() throws Exception {
        MockEndpoint mock = setupExceptionMock();
        InputStream payload = XmlSignatureTest.class.getResourceAsStream("/org/apache/camel/component/xmlsecurity/ExampleDetached.xml");
        assertNotNull("Cannot load payload", payload);
        sendBody("direct:invalidhash", payload);
        assertMockEndpointsSatisfied();
        checkThrownException(mock, XmlSignatureInvalidContentHashException.class, null);
    }

    @Test
    public void testVerifyMantifestInvalidContentHashException() throws Exception {
        MockEndpoint mock = setupExceptionMock();
        InputStream payload = XmlSignatureTest.class
                .getResourceAsStream("/org/apache/camel/component/xmlsecurity/ManifestTest_TamperedContent.xml");
        assertNotNull("Cannot load payload", payload);
        sendBody("direct:invalidhash", payload);
        assertMockEndpointsSatisfied();
        checkThrownException(mock, XmlSignatureInvalidContentHashException.class, null);
    }

    @Test
    public void testVerifySetCryptoContextProperties() throws Exception {
        // although the content referenced by the manifest was tempered, this is
        // not detected by
        // the core validation because the manifest validation is switched off
        // by the crypto context properties
        setupMock("some text tampered");
        InputStream payload = XmlSignatureTest.class
                .getResourceAsStream("/org/apache/camel/component/xmlsecurity/ManifestTest_TamperedContent.xml");
        assertNotNull("Cannot load payload", payload);
        sendBody("direct:cryptocontextprops", payload);
        assertMockEndpointsSatisfied();
    }

    @Test
    public void testVerifySignatureInvalidValueException() throws Exception {
        MockEndpoint mock = setupExceptionMock();
        setUpKeys("DSA", 512);
        context.getEndpoint("xmlsecurity:verify://verifyexceptions?keySelector=#selector", XmlVerifierEndpoint.class).setKeySelector(
                KeySelector.singletonKeySelector(keyPair.getPublic()));
        // payload needs DSA key
        InputStream payload = XmlSignatureTest.class
                .getResourceAsStream("/org/apache/camel/component/xmlsecurity/ExampleEnvelopingDigSig.xml");
        assertNotNull("Cannot load payload", payload);
        sendBody("direct:verifyexceptions", payload);
        assertMockEndpointsSatisfied();
        checkThrownException(mock, XmlSignatureInvalidValueException.class, null);
    }

    @Test
    public void testVerifyInvalidKeyException() throws Exception {
        MockEndpoint mock = setupExceptionMock();
        InputStream payload = XmlSignatureTest.class
                .getResourceAsStream("/org/apache/camel/component/xmlsecurity/ExampleEnvelopingDigSig.xml");
        assertNotNull("Cannot load payload", payload);
        sendBody("direct:verifyInvalidKeyException", payload);
        assertMockEndpointsSatisfied();
        checkThrownException(mock, XmlSignatureInvalidKeyException.class, null);
    }

    @Test
    public void testUriDereferencerAndBaseUri() throws Exception {
        setupMock();
        sendBody("direct:uridereferencer", payload);
        assertMockEndpointsSatisfied();
    }

    @Test
    public void testVerifyXmlSignatureChecker() throws Exception {
        MockEndpoint mock = setupExceptionMock();
        InputStream payload = XmlSignatureTest.class
                .getResourceAsStream("/org/apache/camel/component/xmlsecurity/ExampleEnvelopedXmlSig.xml");
        assertNotNull("Cannot load payload", payload);
        sendBody("direct:xmlSignatureChecker", payload);
        assertMockEndpointsSatisfied();
        checkThrownException(mock, XmlSignatureInvalidException.class, null);
    }

    @Test
    public void testVerifyValidationFailedHandler() throws Exception {
        setupMock("some text tampered");
        InputStream payload = XmlSignatureTest.class
                .getResourceAsStream("/org/apache/camel/component/xmlsecurity/ManifestTest_TamperedContent.xml");
        assertNotNull("Cannot load payload", payload);
        sendBody("direct:validationFailedHandler", payload);
        assertMockEndpointsSatisfied();
    }

    @Test
    public void testFurtherParameters() throws Exception {
        setupMock(payload);
        String payloadWithDTDoctype = "<?xml version=\'1.0\'?>" + "<!DOCTYPE Signature SYSTEM "
                + "\"src/test/resources/org/apache/camel/component/xmlsecurity/xmldsig-core-schema.dtd\" [ <!ENTITY dsig "
                + "\"http://www.w3.org/2000/09/xmldsig#\"> ]>" + "<root xmlns=\"http://test/test\"><test>Test Message</test></root>";

        sendBody("direct:furtherparams", payloadWithDTDoctype);
        assertMockEndpointsSatisfied();
    }

    @Test
    public void testReferenceUriWithIdAttributeInTheEnvelopedCase() throws Exception {

        XmlSignerEndpoint endpoint = getDetachedSignerEndpoint();
        endpoint.setParentLocalName("root");
        endpoint.setParentNamespace("http://test");
        endpoint.setXpathsToIdAttributes(null);

        String detachedPayload = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + //
                "<ns:root xmlns:ns=\"http://test\"><a ID=\"myID\"><b>bValue</b></a></ns:root>";
        MockEndpoint mock = getMockEndpoint("mock:result");
        mock.expectedMessageCount(1);
        sendBody("direct:detached", detachedPayload,
                Collections.singletonMap(XmlSignatureConstants.HEADER_CONTENT_REFERENCE_URI, (Object) "#myID"));
        assertMockEndpointsSatisfied();
        String expectedPartContent = "<ds:Reference URI=\"#myID\">";
        checkBodyContains(mock, expectedPartContent);
    }

    @Test
    public void testDetachedSignature() throws Exception {
        testDetachedSignatureInternal();
    }

    @Test
    public void testSignatureIdAtributeNull() throws Exception {
        // the signature Id parameter must be empty, this is set in the URI
        // already
        Element sigEle = testDetachedSignatureInternal();
        Attr attr = sigEle.getAttributeNode("Id");
        assertNull("Signature element contains Id attribute", attr);
    }

    @Test
    public void testSignatureIdAttribute() throws Exception {
        String signatureId = "sigId";
        XmlSignerEndpoint endpoint = getDetachedSignerEndpoint();
        endpoint.setSignatureId(signatureId);
        Element sigEle = testDetachedSignatureInternal();
        String value = sigEle.getAttribute("Id");
        assertNotNull("Signature Id is null", value);
        assertEquals(signatureId, value);
    }

    @Test
    public void testSignatureIdAttributeGenerated() throws Exception {
        String signatureId = null;
        XmlSignerEndpoint endpoint = getDetachedSignerEndpoint();
        endpoint.setSignatureId(signatureId);
        Element sigEle = testDetachedSignatureInternal();
        String value = sigEle.getAttribute("Id");
        assertNotNull("Signature Id is null", value);
        assertTrue("Signature Id value does not start with '_'", value.startsWith("_"));
    }

    private Element testDetachedSignatureInternal() throws InterruptedException, XPathExpressionException, SAXException, IOException,
            ParserConfigurationException {
        String detachedPayload = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + //
                "<ns:root xmlns:ns=\"http://test\"><a ID=\"myID\"><b>bValue</b></a></ns:root>";
        MockEndpoint mock = getMockEndpoint("mock:result");
        mock.expectedMessageCount(1);
        MockEndpoint mockVerified = getMockEndpoint("mock:verified");
        mockVerified.expectedBodiesReceived(detachedPayload);
        sendBody("direct:detached", detachedPayload);
        assertMockEndpointsSatisfied();
        Map<String, String> namespaceMap = new TreeMap<String, String>();
        namespaceMap.put("ns", "http://test");
        namespaceMap.put("ds", XMLSignature.XMLNS);
        Object obj = checkXpath(mock, "ns:root/ds:Signature", namespaceMap);
        Element sigEle = (Element) obj;
        return sigEle;
    }

    @Test
    public void testDetachedSignatureComplexSchema() throws Exception {

        String xpath1exp = "/ns:root/test/ns1:B/C/@ID";
        String xpath2exp = "/ns:root/test/@ID";
        testDetached2Xpaths(xpath1exp, xpath2exp);
    }

    /**
     * Checks that the processor sorts the xpath expressions in such a way that
     * elements with deeper hierarchy level are signed first.
     *
     */
    @Test
    public void testDetachedSignatureWrongXPathOrder() throws Exception {

        String xpath2exp = "/ns:root/test/ns1:B/C/@ID";
        String xpath1exp = "/ns:root/test/@ID";
        testDetached2Xpaths(xpath1exp, xpath2exp);
    }

    void testDetached2Xpaths(String xpath1exp, String xpath2exp) throws InterruptedException, XPathExpressionException, SAXException,
            IOException, ParserConfigurationException {
        String detachedPayload = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + //
                "<ns:root xmlns:ns=\"http://test\"><test ID=\"myID\"><b>bValue</b><ts:B xmlns:ts=\"http://testB\"><C ID=\"cID\"><D>dvalue</D></C></ts:B></test></ns:root>";
        MockEndpoint mock = getMockEndpoint("mock:result");
        mock.expectedMessageCount(1);
        MockEndpoint mockVerified = getMockEndpoint("mock:verified");
        mockVerified.expectedBodiesReceived(detachedPayload);
        Map<String, Object> headers = new TreeMap<String, Object>();
        headers.put(XmlSignatureConstants.HEADER_SCHEMA_RESOURCE_URI, (Object) "org/apache/camel/component/xmlsecurity/TestComplex.xsd");
        Map<String, String> namespaceMap = new TreeMap<String, String>();
        namespaceMap.put("ns", "http://test");
        namespaceMap.put("ns1", "http://testB");
        XPathFilterParameterSpec xpath1 = XmlSignatureHelper.getXpathFilter(xpath1exp, namespaceMap);
        XPathFilterParameterSpec xpath2 = XmlSignatureHelper.getXpathFilter(xpath2exp, namespaceMap);

        List<XPathFilterParameterSpec> xpaths = new ArrayList<XPathFilterParameterSpec>();
        xpaths.add(xpath1);
        xpaths.add(xpath2);
        headers.put(XmlSignatureConstants.HEADER_XPATHS_TO_ID_ATTRIBUTES, (Object) xpaths);
        sendBody("direct:detached", detachedPayload, headers);
        assertMockEndpointsSatisfied();
        Map<String, String> namespaceMap2 = new TreeMap<String, String>();
        namespaceMap2.put("ns", "http://test");
        namespaceMap2.put("ds", XMLSignature.XMLNS);
        namespaceMap2.put("nsB", "http://testB");
        checkXpath(mock, "ns:root/test/nsB:B/ds:Signature", namespaceMap2);
        checkXpath(mock, "ns:root/ds:Signature", namespaceMap2);
    }

    @Test
    public void testExceptionEnvelopedAndDetached() throws Exception {
        String detachedPayload = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + //
                "<ns:root xmlns:ns=\"http://test\"><a ID=\"myID\"><b>bValue</b></a></ns:root>";
        XmlSignerEndpoint endpoint = getDetachedSignerEndpoint();
        String parentLocalName = "parent";
        endpoint.setParentLocalName(parentLocalName);
        MockEndpoint mock = setupExceptionMock();
        mock.expectedMessageCount(1);
        sendBody("direct:detached", detachedPayload);
        assertMockEndpointsSatisfied();
        checkThrownException(
                mock,
                XmlSignatureException.class,
                "The configuration of the XML signer component is wrong. The parent local name "
                        + parentLocalName
                        + " for an enveloped signature and the XPATHs to ID attributes for a detached signature are specified. You must not specify both parameters.",
                null);
    }

    @Test
    public void testExceptionSchemaValidation() throws Exception {
        String detachedPayload = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + //
                "<ns:root xmlns:ns=\"http://test\"><a ID=\"myID\"><error>bValue</error></a></ns:root>";
        MockEndpoint mock = setupExceptionMock();
        mock.expectedMessageCount(1);
        sendBody("direct:detached", detachedPayload);
        assertMockEndpointsSatisfied();
        checkThrownException(mock, SchemaValidationException.class, null);
    }

    @Test
    public void testEceptionDetachedNoXmlSchema() throws Exception {
        String detachedPayload = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + //
                "<ns:root xmlns:ns=\"http://test\"><a ID=\"myID\"><b>bValue</b></a></ns:root>";
        XmlSignerEndpoint endpoint = getDetachedSignerEndpoint();
        endpoint.setSchemaResourceUri(null);
        MockEndpoint mock = setupExceptionMock();
        mock.expectedMessageCount(1);
        sendBody("direct:detached", detachedPayload);
        assertMockEndpointsSatisfied();
        checkThrownException(mock, XmlSignatureException.class,
                "The configruation of the XML Signature component is wrong: No XML schema specified in the detached case", null);
    }

    @Test
    public void testExceptionDetachedXpathInvalid() throws Exception {
        String wrongXPath = "n1:p/a"; // namespace prefix is not defined
        MockEndpoint mock = testXpath(wrongXPath);
        checkThrownException(mock, XmlSignatureException.class, "The configured xpath expression " + wrongXPath + " is invalid.",
                XPathExpressionException.class);
    }

    @Test
    public void testExceptionDetachedXPathNoIdAttribute() throws Exception {
        String value = "not id";
        String detachedPayload = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + //
                "<ns:root xmlns:ns=\"http://test\"><a ID=\"myID\" stringAttr=\"" + value + "\"><b>bValue</b></a></ns:root>";
        String xPath = "a/@stringAttr";

        MockEndpoint mock = testXpath(xPath, detachedPayload);
        checkThrownException(mock, XmlSignatureException.class,
                "Wrong configured xpath expression for ID attributes: The evaluation of the xpath expression " + xPath
                        + " resulted in an attribute which is not of type ID. The attribute value is " + value + ".", null);
    }

    @Test
    public void testExceptionDetachedXpathNoAttribute() throws Exception {
        String xPath = "a"; // Element a
        MockEndpoint mock = testXpath(xPath);
        checkThrownException(mock, XmlSignatureException.class,
                "Wrong configured xpath expression for ID attributes: The evaluation of the xpath expression " + xPath
                        + " returned a node which was not of type Attribute.", null);
    }

    @Test
    public void testExceptionDetachedXPathNoResult() throws Exception {
        String xPath = "a/@stringAttr"; // for this xpath there is no result
        MockEndpoint mock = testXpath(xPath);
        checkThrownException(
                mock,
                XmlSignatureException.class,
                "No element to sign found in the detached case. No node found for the configured xpath expressions "
                        + xPath
                        + ". Either the configuration of the XML signature component is wrong or the incoming message has not the correct structure.",
                null);
    }

    private MockEndpoint testXpath(String xPath) throws InterruptedException {
        String detachedPayload = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + //
                "<ns:root xmlns:ns=\"http://test\"><a ID=\"myID\"><b>bValue</b></a></ns:root>";
        return testXpath(xPath, detachedPayload);
    }

    private MockEndpoint testXpath(String xPath, String detachedPayload) throws InterruptedException {
        MockEndpoint mock = setupExceptionMock();
        mock.expectedMessageCount(1);
        List<XPathFilterParameterSpec> list = Collections.singletonList(XmlSignatureHelper.getXpathFilter(xPath, null));
        sendBody("direct:detached", detachedPayload,
                Collections.singletonMap(XmlSignatureConstants.HEADER_XPATHS_TO_ID_ATTRIBUTES, (Object) list));
        assertMockEndpointsSatisfied();
        return mock;
    }

    @Test
    public void testExceptionDetachedNoParent() throws Exception {
        String detachedPayload = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + //
                "<ns:root ID=\"rootId\" xmlns:ns=\"http://test\"><a ID=\"myID\"><b>bValue</b></a></ns:root>";
        String xPath = "//@ID";
        String localName = "root";
        String namespaceURI = "http://test";
        String referenceUri = "#rootId";
        MockEndpoint mock = testXpath(xPath, detachedPayload);
        checkThrownException(mock, XmlSignatureException.class,
                "Either the configuration of the XML Signature component is wrong or the incoming document has an invalid structure: The element "
                        + localName + "{" + namespaceURI + "} which is referenced by the reference URI " + referenceUri
                        + " has no parent element. The element must have a parent element in the configured detached case.", null);
    }

    @Test
    public void testOutputXmlEncodingEnveloping() throws Exception {

        String inputEncoding = "UTF-8";
        String signerEncoding = "UTF-16LE";
        String outputEncoding = "ISO-8859-1"; // latin 1

        String signerEndpointUri = getSignerEndpointURIEnveloping();
        String verifierEndpointUri = getVerifierEncpointURIEnveloping();

        String directStart = "direct:enveloping";

        checkOutputEncoding(inputEncoding, signerEncoding, outputEncoding, signerEndpointUri, verifierEndpointUri, directStart);
    }

    String getVerifierEncpointURIEnveloping() {
        return "xmlsecurity:verify://enveloping?keySelector=#selector";
    }

    String getSignerEndpointURIEnveloping() {
        return "xmlsecurity:sign://enveloping?keyAccessor=#accessor&schemaResourceUri=";
    }

    @Test
    public void testOutputXmlEncodingEnveloped() throws Exception {

        String inputEncoding = "UTF-8";
        String signerEncoding = "UTF-16LE";
        String outputEncoding = "ISO-8859-1"; // latin 1

        String signerEndpointUri = getSignerEndpointURIEnveloped();
        String verifierEndpointUri = getVerifierEndpointURIEnveloped();

        String directStart = "direct:enveloped";

        checkOutputEncoding(inputEncoding, signerEncoding, outputEncoding, signerEndpointUri, verifierEndpointUri, directStart);
    }

    String getVerifierEndpointURIEnveloped() {
        return "xmlsecurity:verify://enveloped?keySelector=#selector";
    }

    String getSignerEndpointURIEnveloped() {
        return "xmlsecurity:sign://enveloped?keyAccessor=#accessor&parentLocalName=root&parentNamespace=http://test/test";
    }

    private byte[] getPayloadForEncoding(String encoding) {
        String s = "<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>\n"
                + "<root xmlns=\"http://test/test\"><test>Test Message</test></root>";
        return s.getBytes(Charset.forName(encoding));
    }

    @Test
    public void testExceptionParentLocalNameAndXPathSet() throws Exception {

        XmlSignerEndpoint endpoint = getSignatureEncpointForSignException();
        MockEndpoint mock = setupExceptionMock();
        try {
            endpoint.setParentXpath(getNodeSerachXPath());
            endpoint.setParentLocalName("root");
            sendBody("direct:signexceptions", payload);
            assertMockEndpointsSatisfied();
            checkThrownException(mock, XmlSignatureException.class, "The configuration of the XML signer component is wrong. " + //
                    "The parent local name root and the parent XPath //pre:root are specified. You must not specify both parameters.", null);
        } finally {
            endpoint.setParentXpath(null);
            endpoint.setParentLocalName(null);
        }
    }

    @Test
    public void testExceptionXpathsToIdAttributesNameAndXPathSet() throws Exception {

        XmlSignerEndpoint endpoint = getSignatureEncpointForSignException();
        MockEndpoint mock = setupExceptionMock();
        try {
            endpoint.setParentXpath(getNodeSerachXPath());
            List<XPathFilterParameterSpec> xpaths = Collections.singletonList(XmlSignatureHelper.getXpathFilter("/ns:root/a/@ID", null));
            endpoint.setXpathsToIdAttributes(xpaths);
            sendBody("direct:signexceptions", payload);
            assertMockEndpointsSatisfied();
            checkThrownException(
                    mock,
                    XmlSignatureException.class,
                    "The configuration of the XML signer component is wrong. " + //
                            "The parent XPath //pre:root for an enveloped signature and the XPATHs to ID attributes for a detached signature are specified. You must not specify both parameters.",
                    null);
        } finally {
            endpoint.setParentXpath(null);
            endpoint.setXpathsToIdAttributes(null);
        }
    }

    @Test
    public void testExceptionInvalidParentXpath() throws Exception {

        XmlSignerEndpoint endpoint = getSignatureEncpointForSignException();
        MockEndpoint mock = setupExceptionMock();
        try {
            endpoint.setParentXpath(XmlSignatureHelper.getXpathFilter("//pre:root", null)); // invalid xpath: namespace-prefix mapping is missing
            sendBody("direct:signexceptions", payload);
            assertMockEndpointsSatisfied();
            checkThrownException(mock, XmlSignatureException.class,
                    "The parent XPath //pre:root is wrongly configured: The XPath //pre:root is invalid.", null);
        } finally {
            endpoint.setParentXpath(null);
        }
    }

    @Test
    public void testExceptionParentXpathWithNoResult() throws Exception {

        XmlSignerEndpoint endpoint = getSignatureEncpointForSignException();
        MockEndpoint mock = setupExceptionMock();
        try {
            endpoint.setParentXpath(XmlSignatureHelper.getXpathFilter("//root", null)); // xpath with no result
            sendBody("direct:signexceptions", payload);
            assertMockEndpointsSatisfied();
            checkThrownException(mock, XmlSignatureException.class,
                    "The parent XPath //root returned no result. Check the configuration of the XML signer component.", null);
        } finally {
            endpoint.setParentXpath(null);
        }
    }

    XmlSignerEndpoint getSignatureEncpointForSignException() {
        XmlSignerEndpoint endpoint = (XmlSignerEndpoint) context().getEndpoint("xmlsecurity:sign://signexceptions?keyAccessor=#accessor" + //
                "&signatureAlgorithm=http://www.w3.org/2001/04/xmldsig-more#rsa-sha512");
        return endpoint;
    }

    @Test
    public void testExceptionParentXpathWithNoElementResult() throws Exception {

        XmlSignerEndpoint endpoint = getSignatureEncpointForSignException();
        MockEndpoint mock = setupExceptionMock();
        try {
            String myPayload = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + //
                    "<ns:root ID=\"rootId\" xmlns:ns=\"http://test\"></ns:root>";
            endpoint.setParentXpath(XmlSignatureHelper.getXpathFilter("/pre:root/@ID", Collections.singletonMap("pre", "http://test"))); // xpath with no element result
            sendBody("direct:signexceptions", myPayload);
            assertMockEndpointsSatisfied();
            checkThrownException(mock, XmlSignatureException.class,
                    "The parent XPath /pre:root/@ID returned no element. Check the configuration of the XML signer component.", null);
        } finally {
            endpoint.setParentXpath(null);
        }
    }

    @Test
    public void testEnvelopedSignatureWithParentXpath() throws Exception {
        String myPayload = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
                + "<ns:root xmlns:ns=\"http://test\"><a>a1</a><a/><test>Test Message</test></ns:root>";
        setupMock(myPayload);
        sendBody("direct:envelopedParentXpath", myPayload);
        assertMockEndpointsSatisfied();
    }

    XmlSignerEndpoint getDetachedSignerEndpoint() {
        XmlSignerEndpoint endpoint = (XmlSignerEndpoint) context().getEndpoint(
                "xmlsecurity:sign://detached?keyAccessor=#keyAccessorDefault&xpathsToIdAttributes=#xpathsToIdAttributes&"//
                        + "schemaResourceUri=org/apache/camel/component/xmlsecurity/Test.xsd&signatureId=&clearHeaders=false");
        return endpoint;
    }

    private void checkOutputEncoding(String inputEncoding, String signerEncoding, String outputEncoding, String signerEndpointUri,
            String verifierEndpointUri, String directStart) throws InterruptedException, UnsupportedEncodingException {
        byte[] inputPayload = getPayloadForEncoding(inputEncoding);
        byte[] expectedPayload = getPayloadForEncoding(outputEncoding);

        MockEndpoint mock = getMockEndpoint("mock:result");
        mock.expectedBodiesReceived(expectedPayload);

        MockEndpoint mockSigned = getMockEndpoint("mock:signed");
        mock.expectedMessageCount(1);

        XmlSignerEndpoint endpointSigner = (XmlSignerEndpoint) context().getEndpoint(signerEndpointUri);

        XmlVerifierEndpoint endpoinVerifier = (XmlVerifierEndpoint) context().getEndpoint(verifierEndpointUri);
        try {
            endpointSigner.setOutputXmlEncoding(signerEncoding);
            endpoinVerifier.setOutputXmlEncoding(outputEncoding);
            sendBody(directStart, inputPayload);
            assertMockEndpointsSatisfied();
            Message signedMessage = mockSigned.getExchanges().get(0).getIn();
            byte[] signedBytes = signedMessage.getBody(byte[].class);
            String signedPayload = new String(signedBytes, signerEncoding);
            assertTrue(signedPayload.contains(signerEncoding));
            String charsetHeaderSigner = signedMessage.getHeader(Exchange.CHARSET_NAME, String.class);
            assertEquals(signerEncoding, charsetHeaderSigner);
            String charsetHeaderVerifier = mock.getExchanges().get(0).getIn().getHeader(Exchange.CHARSET_NAME, String.class);
            assertEquals(outputEncoding, charsetHeaderVerifier);
        } finally {
            endpointSigner.setOutputXmlEncoding(null);
            endpoinVerifier.setOutputXmlEncoding(null);
        }
    }

    private void checkBodyContains(MockEndpoint mock, String expectedPartContent) {
        Message message = getMessage(mock);
        String body = message.getBody(String.class);
        assertNotNull(body);
        assertTrue("The message body " + body + " does not contain the expected string " + expectedPartContent,
                body.contains(expectedPartContent));
    }

    private Object checkXpath(MockEndpoint mock, String xpathString, final Map<String, String> prefix2Namespace)
        throws XPathExpressionException, SAXException, IOException, ParserConfigurationException {
        Message mess = getMessage(mock);
        InputStream body = mess.getBody(InputStream.class);
        assertNotNull(body);
        XPathFactory xpathFactory = XPathFactory.newInstance();
        XPath xpath = xpathFactory.newXPath();
        NamespaceContext nc = new NamespaceContext() {

            @SuppressWarnings("rawtypes")
            @Override
            public Iterator getPrefixes(String namespaceURI) {
                return null;
            }

            @Override
            public String getPrefix(String namespaceURI) {
                return null;
            }

            @Override
            public String getNamespaceURI(String prefix) {
                return prefix2Namespace.get(prefix);
            }
        };
        xpath.setNamespaceContext(nc);
        XPathExpression expr = xpath.compile(xpathString);
        Object result = expr.evaluate(XmlSignatureHelper.newDocumentBuilder(true).parse(body), XPathConstants.NODE);
        assertNotNull("The xpath " + xpathString + " returned a null value", result);
        return result;
    }

    private Message getMessage(MockEndpoint mock) {
        List<Exchange> exs = mock.getExchanges();
        assertNotNull(exs);
        assertEquals(1, exs.size());
        Exchange ex = exs.get(0);
        Message mess = ex.getIn();
        assertNotNull(mess);
        return mess;
    }

    private void checkThrownException(MockEndpoint mock, Class<? extends Exception> cl, Class<? extends Exception> expectedCauseClass)
        throws Exception {
        checkThrownException(mock, cl, null, expectedCauseClass);
    }

    private void checkThrownException(MockEndpoint mock, Class<? extends Exception> cl, String expectedMessage,
            Class<? extends Exception> expectedCauseClass) throws Exception {
        Exception e = (Exception) mock.getExchanges().get(0).getProperty(Exchange.EXCEPTION_CAUGHT);
        assertNotNull("Expected excpetion " + cl.getName() + " missing", e);
        if (e.getClass() != cl) {
            String stackTrace = getStrackTrace(e);
            fail("Exception  " + cl.getName() + " excpected, but was " + e.getClass().getName() + ": " + stackTrace);
        }
        if (expectedMessage != null) {
            assertEquals(expectedMessage, e.getMessage());
        }
        if (expectedCauseClass != null) {
            Throwable cause = e.getCause();
            assertNotNull("Expected cause exception" + expectedCauseClass.getName() + " missing", cause);
            if (expectedCauseClass != cause.getClass()) {
                fail("Cause exception " + expectedCauseClass.getName() + " expected, but was " + cause.getClass().getName() + ": "
                        + getStrackTrace(e));
            }
        }
    }

    private String getStrackTrace(Exception e) throws UnsupportedEncodingException {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        PrintWriter w = new PrintWriter(os);
        e.printStackTrace(w);
        w.close();
        String stackTrace = new String(os.toByteArray(), "UTF-8");
        return stackTrace;
    }

    private MockEndpoint setupExceptionMock() {
        MockEndpoint mock = getMockEndpoint("mock:exception");
        mock.setExpectedMessageCount(1);
        MockEndpoint mockResult = getMockEndpoint("mock:result");
        mockResult.setExpectedMessageCount(0);
        return mock;
    }

    private MockEndpoint setupMock() {
        return setupMock(payload);
    }

    private MockEndpoint setupMock(String payload) {
        MockEndpoint mock = getMockEndpoint("mock:result");
        mock.expectedBodiesReceived(payload);
        return mock;
    }

    public Exchange doTestSignatureRoute(RouteBuilder builder) throws Exception {
        return doSignatureRouteTest(builder, null, Collections.<String, Object> emptyMap());
    }

    public Exchange doSignatureRouteTest(RouteBuilder builder, Exchange e, Map<String, Object> headers) throws Exception {
        CamelContext context = new DefaultCamelContext();
        try {
            context.addRoutes(builder);
            context.start();

            MockEndpoint mock = context.getEndpoint("mock:result", MockEndpoint.class);
            mock.setExpectedMessageCount(1);

            ProducerTemplate template = context.createProducerTemplate();
            if (e != null) {
                template.send("direct:in", e);
            } else {
                template.sendBodyAndHeaders("direct:in", payload, headers);
            }
            assertMockEndpointsSatisfied();
            return mock.getReceivedExchanges().get(0);
        } finally {
            context.stop();
        }
    }

    @Before
    public void setUp() throws Exception {
        setUpKeys("RSA", 1024);
        disableJMX();
        super.setUp();
    }

    public void setUpKeys(String algorithm, int keylength) throws Exception {
        keyPair = getKeyPair(algorithm, keylength);
    }

    public static KeyPair getKeyPair(String algorithm, int keylength) {
        KeyPairGenerator keyGen;
        try {
            keyGen = KeyPairGenerator.getInstance(algorithm);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        keyGen.initialize(keylength, new SecureRandom());
        return keyGen.generateKeyPair();
    }

    public static KeyStore loadKeystore() throws Exception {
        KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
        InputStream in = XmlSignatureTest.class.getResourceAsStream("/bob.keystore");
        keystore.load(in, "letmein".toCharArray());
        return keystore;
    }

    public Certificate getCertificateFromKeyStore() throws Exception {
        Certificate c = loadKeystore().getCertificate("bob");
        return c;
    }

    public PrivateKey getKeyFromKeystore() throws Exception {
        return (PrivateKey) loadKeystore().getKey("bob", "letmein".toCharArray());
    }

    private AlgorithmMethod getCanonicalizationMethod() {
        List<String> inclusivePrefixes = new ArrayList<String>(1);
        inclusivePrefixes.add("ds");
        return XmlSignatureHelper.getCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE, inclusivePrefixes);
    }

    private List<AlgorithmMethod> getTransformsXPath2() {

        List<XPathAndFilter> list = new ArrayList<XPathAndFilter>(3);
        XPathAndFilter xpath1 = new XPathAndFilter("//n0:ToBeSigned", XPathType.Filter.INTERSECT.toString());
        list.add(xpath1);
        XPathAndFilter xpath2 = new XPathAndFilter("//n0:NotToBeSigned", XPathType.Filter.SUBTRACT.toString());
        list.add(xpath2);
        XPathAndFilter xpath3 = new XPathAndFilter("//n0:ReallyToBeSigned", XPathType.Filter.UNION.toString());
        list.add(xpath3);
        List<AlgorithmMethod> result = new ArrayList<AlgorithmMethod>(2);
        result.add(XmlSignatureHelper.getCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE));
        result.add(XmlSignatureHelper.getXPath2Transform(list, getNamespaceMap()));
        return result;
    }

    private Map<String, String> getNamespaceMap() {
        Map<String, String> result = new HashMap<String, String>(1);
        result.put("n0", "http://test/test");
        return result;
    }

    private List<AlgorithmMethod> getTransformsXsltXpath() {
        try {
            AlgorithmMethod transformXslt = XmlSignatureHelper.getXslTransform("/org/apache/camel/component/xmlsecurity/xslt_test.xsl");
            Map<String, String> namespaceMap = new HashMap<String, String>(1);
            namespaceMap.put("n0", "https://org.apache/camel/xmlsecurity/test");
            AlgorithmMethod transformXpath = XmlSignatureHelper.getXPathTransform("//n0:XMLSecurity/n0:Content", namespaceMap);
            // I removed base 64 transform because the JDK implementation does
            // not correctly support this transformation
            // AlgorithmMethod transformBase64 = helper.getBase64Transform();
            List<AlgorithmMethod> result = new ArrayList<AlgorithmMethod>(3);
            result.add(XmlSignatureHelper.getCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE));
            result.add(transformXslt);
            result.add(transformXpath);
            // result.add(transformBase64);
            return result;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    static KeyAccessor getKeyAccessor(final PrivateKey privateKey) {
        KeyAccessor accessor = new KeyAccessor() {

            @Override
            public KeySelector getKeySelector(Message message) throws Exception {
                return KeySelector.singletonKeySelector(privateKey);
            }

            @Override
            public KeyInfo getKeyInfo(Message mess, Node messageBody, KeyInfoFactory keyInfoFactory) throws Exception {
                return null;
            }
        };
        return accessor;
    }

    public static String getBaseUri() {
        String uri = "file:/" + System.getProperty("user.dir") + "/src/test/resources/org/apache/camel/component/xmlsecurity/";
        return uri.replace('\\', '/');
    }

    public static KeySelector getKeyValueKeySelector() {
        return new KeyValueKeySelector();
    }

    /**
     * KeySelector which retrieves the public key from the KeyValue element and
     * returns it. NOTE: If the key algorithm doesn't match signature algorithm,
     * then the public key will be ignored.
     */
    static class KeyValueKeySelector extends KeySelector {
        public KeySelectorResult select(KeyInfo keyInfo, KeySelector.Purpose purpose, AlgorithmMethod method, XMLCryptoContext context)
            throws KeySelectorException {
            if (keyInfo == null) {
                throw new KeySelectorException("Null KeyInfo object!");
            }

            SignatureMethod sm = (SignatureMethod) method;
            @SuppressWarnings("rawtypes")
            List list = keyInfo.getContent();

            for (int i = 0; i < list.size(); i++) {
                XMLStructure xmlStructure = (XMLStructure) list.get(i);
                if (xmlStructure instanceof KeyValue) {
                    PublicKey pk = null;
                    try {
                        pk = ((KeyValue) xmlStructure).getPublicKey();
                    } catch (KeyException ke) {
                        throw new KeySelectorException(ke);
                    }
                    // make sure algorithm is compatible with method
                    if (algEquals(sm.getAlgorithm(), pk.getAlgorithm())) {
                        return new SimpleKeySelectorResult(pk);
                    }
                }
            }
            throw new KeySelectorException("No KeyValue element found!");
        }

        static boolean algEquals(String algURI, String algName) {
            return (algName.equalsIgnoreCase("DSA") && algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1))
                    || (algName.equalsIgnoreCase("RSA") && algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1));
        }
    }

    private static class SimpleKeySelectorResult implements KeySelectorResult {
        private PublicKey pk;

        SimpleKeySelectorResult(PublicKey pk) {
            this.pk = pk;
        }

        public Key getKey() {
            return pk;
        }
    }

    public static Map<String, ? extends Object> getCrytoContextProperties() {
        return Collections.singletonMap("org.jcp.xml.dsig.validateManifests", Boolean.FALSE);
    }

    public static KeyAccessor getDefaultKeyAccessor() throws Exception {
        return TestKeystore.getKeyAccessor("bob");
    }

    public static KeySelector getDefaultKeySelector() throws Exception {
        return TestKeystore.getKeySelector("bob");
    }

    public static KeyAccessor getDefaultKeyAccessorDsa() throws Exception {
        return TestKeystore.getKeyAccessor("bobdsa");
    }

    public static KeySelector getDefaultKeySelectorDsa() throws Exception {
        return TestKeystore.getKeySelector("bobdsa");
    }

    public static XmlSignatureChecker getEnvelopingXmlSignatureChecker() {
        return new EnvelopingXmlSignatureChecker();
    }

    public static XmlSignature2Message getXmlSignature2MessageWithTimestampdProperty() {
        return new XmlSignature2Message2MessageWithTimestampProperty();
    }

    public static ValidationFailedHandler getValidationFailedHandlerIgnoreManifestFailures() {
        return new ValidationFailedHandlerIgnoreManifestFailures();
    }

    public static XmlSignatureProperties getSignatureProperties() {
        return new TimestampProperty();
    }

    public static XPathFilterParameterSpec getNodeSerachXPath() {
        Map<String, String> prefix2Namespace = Collections.singletonMap("pre", "http://test/test");
        return XmlSignatureHelper.getXpathFilter("//pre:root", prefix2Namespace);
    }

    public static URIDereferencer getSameDocumentUriDereferencer() {
        return SameDocumentUriDereferencer.getInstance();
    }

    public static XPathFilterParameterSpec getParentXPathBean() {
        Map<String, String> prefix2Namespace = Collections.singletonMap("ns", "http://test");
        return XmlSignatureHelper.getXpathFilter("/ns:root/a[last()]", prefix2Namespace);
    }

}
TOP

Related Classes of org.apache.camel.component.xmlsecurity.XmlSignatureTest

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.