| /* |
| * 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.processor; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.UnsupportedEncodingException; |
| import java.security.GeneralSecurityException; |
| import java.security.InvalidAlgorithmParameterException; |
| import java.security.InvalidKeyException; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.NoSuchProviderException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.UUID; |
| |
| import javax.xml.crypto.AlgorithmMethod; |
| import javax.xml.crypto.KeySelector; |
| import javax.xml.crypto.dom.DOMStructure; |
| import javax.xml.crypto.dsig.CanonicalizationMethod; |
| import javax.xml.crypto.dsig.DigestMethod; |
| import javax.xml.crypto.dsig.Reference; |
| import javax.xml.crypto.dsig.SignatureMethod; |
| import javax.xml.crypto.dsig.SignedInfo; |
| import javax.xml.crypto.dsig.Transform; |
| import javax.xml.crypto.dsig.XMLObject; |
| import javax.xml.crypto.dsig.XMLSignature; |
| import javax.xml.crypto.dsig.XMLSignatureException; |
| import javax.xml.crypto.dsig.XMLSignatureFactory; |
| import javax.xml.crypto.dsig.dom.DOMSignContext; |
| import javax.xml.crypto.dsig.keyinfo.KeyInfo; |
| import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec; |
| import javax.xml.crypto.dsig.spec.TransformParameterSpec; |
| import javax.xml.crypto.dsig.spec.XPathFilterParameterSpec; |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.ParserConfigurationException; |
| import javax.xml.validation.Schema; |
| import javax.xml.xpath.XPathConstants; |
| import javax.xml.xpath.XPathExpression; |
| import javax.xml.xpath.XPathExpressionException; |
| |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| |
| import org.xml.sax.ErrorHandler; |
| import org.xml.sax.SAXException; |
| |
| import org.apache.camel.Exchange; |
| import org.apache.camel.Message; |
| import org.apache.camel.component.xmlsecurity.api.KeyAccessor; |
| import org.apache.camel.component.xmlsecurity.api.SignatureType; |
| 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.XmlSignatureInvalidKeyException; |
| import org.apache.camel.component.xmlsecurity.api.XmlSignatureNoKeyException; |
| import org.apache.camel.component.xmlsecurity.api.XmlSignatureProperties; |
| import org.apache.camel.support.processor.validation.DefaultValidationErrorHandler; |
| import org.apache.camel.support.processor.validation.ValidatorErrorHandler; |
| import org.apache.camel.util.IOHelper; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Creates from the message body a XML signature element which is returned in |
| * the message body of the output message. Enveloped, enveloping XML, and |
| * detached signatures are supported. |
| * <p> |
| * In the enveloped XML signature case, the method |
| * {@link XmlSignerConfiguration#getParentLocalName()} must not return |
| * <code>null</code>. In this case the parent element must be contained in the |
| * XML document provided by the message body and the signature element is added |
| * as last child element of the parent element. If a KeyInfo instance is |
| * provided by the {@link KeyAccessor} and |
| * {@link XmlSignerConfiguration#getAddKeyInfoReference()} is <code>true</code>, |
| * then also a reference to the KeyInfo element is added. The generated XML |
| * signature has the following structure: |
| * |
| * <pre> |
| * {@code |
| * <[parent element]> |
| * ... |
| * <Signature Id="[signature_id]"> |
| * <SignedInfo> |
| * <Reference URI=""> |
| * <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/> |
| * (<Transform>)* |
| * <DigestMethod> |
| * <DigestValue> |
| * </Reference> |
| * (<Reference URI="#[keyinfo_Id]"> |
| * <Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/> |
| * <DigestMethod> |
| * <DigestValue> |
| * </Reference>)? |
| * <!-- further references possible, see XmlSignerConfiguration#setProperties(XmlSignatureProperties) --> |
| * </SignedInfo> |
| * <SignatureValue> |
| * (<KeyInfo Id="[keyinfo_id]">)? |
| * <!-- Object elements possible, see XmlSignerConfiguration#setProperties(XmlSignatureProperties) --> |
| * </Signature> |
| * </[parent element]> |
| * } |
| * </pre> |
| * <p> |
| * In the enveloping XML signature case, the generated XML signature has the |
| * following structure: |
| * |
| * <pre> |
| * {@code |
| * <Signature Id="[signature_id]"> |
| * <SignedInfo> |
| * <Reference URI="#[object_id]" type="[optional_type_value]"> |
| * (<Transform>)* |
| * <DigestMethod> |
| * <DigestValue> |
| * </Reference> |
| * (<Reference URI="#[keyinfo_id]"> |
| * <Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/> |
| * <DigestMethod> |
| * <DigestValue> |
| * </Reference>)? |
| * <!-- further references possible, see XmlSignerConfiguration#setProperties(XmlSignatureProperties) --> |
| * </SignedInfo> |
| * <SignatureValue> |
| * (<KeyInfo Id="[keyinfo_id]">)? |
| * <Object Id="[object_id]"/> |
| * <!-- further Object elements possible, see XmlSignerConfiguration#setProperties(XmlSignatureProperties) --> |
| * </Signature> |
| * } |
| * </pre> |
| * |
| * In the enveloping XML signature case, also message bodies containing plain |
| * text are supported. This must be indicated via the header |
| * {@link XmlSignatureConstants#HEADER_MESSAGE_IS_PLAIN_TEXT} or via the |
| * configuration {@link XmlSignerConfiguration#getPlainText()}. |
| * |
| * <p> |
| * Detached signatures where the signature element is a sibling element to the |
| * signed element are supported. Those elements can be signed which have ID |
| * attributes. The elements to be signed must be specified via xpath expressions |
| * (see {@link XmlSignerConfiguration#setXpathsToIdAttributes(List)}) and the |
| * XML schema must be provided via the schema resource URI (see method |
| * {@link XmlSignerConfiguration#setSchemaResourceUri(String)}. Elements with |
| * deeper hierarchy level are signed first. This procedure can result in nested |
| * signatures. |
| * |
| * <p> |
| * In all cases, the digest algorithm is either read from the configuration |
| * method {@link XmlSignerConfiguration#getDigestAlgorithm()} or calculated from |
| * the signature algorithm ( |
| * {@link XmlSignerConfiguration#getSignatureAlgorithm()}. The optional |
| * transforms are read from {@link XmlSignerConfiguration#getTransformMethods()} |
| * . |
| * <p> |
| * In all cases, you can add additional references and objects which contain |
| * properties for the XML signature, see |
| * {@link XmlSignerConfiguration#setProperties(XmlSignatureProperties)}. |
| */ |
| |
| public class XmlSignerProcessor extends XmlSignatureProcessor { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(XmlSignerProcessor.class); |
| |
| private static final String SHA512 = "sha512"; |
| private static final String SHA384 = "sha384"; |
| private static final String SHA256 = "sha256"; |
| private static final String SHA224 = "sha224"; |
| private static final String SHA1 = "sha1"; |
| private static final String RIPEMD160 = "ripemd160"; |
| |
| private static final String HTTP_WWW_W3_ORG_2001_04_XMLDSIG_MORE_SHA224 = |
| "http://www.w3.org/2001/04/xmldsig-more#sha224"; // see RFC 4051 |
| |
| private static final String HTTP_WWW_W3_ORG_2001_04_XMLDSIG_MORE_SHA384 = |
| "http://www.w3.org/2001/04/xmldsig-more#sha384"; // see RFC 4051 |
| |
| private final XmlSignerConfiguration config; |
| |
| public XmlSignerProcessor(XmlSignerConfiguration config) { |
| this.config = config; |
| } |
| |
| @Override |
| public XmlSignerConfiguration getConfiguration() { |
| return config; |
| } |
| |
| @Override |
| public void process(Exchange exchange) throws Exception { //NOPMD |
| |
| try { |
| LOG.debug("XML signature generation started using algorithm {} and canonicalization method {}", getConfiguration() |
| .getSignatureAlgorithm(), getConfiguration().getCanonicalizationMethod().getAlgorithm()); |
| |
| // lets setup the out message before we invoke the signing |
| // so that it can mutate it if necessary |
| Message out = exchange.getOut(); |
| out.copyFrom(exchange.getIn()); |
| |
| Document outputDoc = sign(out); |
| |
| ByteArrayOutputStream outStream = new ByteArrayOutputStream(); |
| XmlSignatureHelper.transformNonTextNodeToOutputStream(outputDoc, outStream, omitXmlDeclaration(out), getConfiguration().getOutputXmlEncoding()); |
| byte[] data = outStream.toByteArray(); |
| out.setBody(data); |
| setOutputEncodingToMessageHeader(out); |
| clearMessageHeaders(out); |
| LOG.debug("XML signature generation finished"); |
| } catch (Exception e) { |
| // remove OUT message, as an exception occurred |
| exchange.setOut(null); |
| throw e; |
| } |
| } |
| |
| protected Document sign(final Message out) throws Exception { //NOPMD |
| |
| try { |
| XMLSignatureFactory fac; |
| // Try to install the Santuario Provider - fall back to the JDK provider if this does |
| // not work |
| try { |
| fac = XMLSignatureFactory.getInstance("DOM", "ApacheXMLDSig"); |
| } catch (NoSuchProviderException ex) { |
| fac = XMLSignatureFactory.getInstance("DOM"); |
| } |
| |
| final Node node = getMessageBodyNode(out); |
| |
| if (getConfiguration().getKeyAccessor() == null) { |
| throw new XmlSignatureNoKeyException( |
| "Key accessor is missing for XML signature generation. Specify a key accessor in the configuration."); |
| } |
| |
| final KeySelector keySelector = getConfiguration().getKeyAccessor().getKeySelector(out); |
| if (keySelector == null) { |
| throw new XmlSignatureNoKeyException( |
| "Key selector is missing for XML signature generation. Specify a key selector in the configuration."); |
| } |
| |
| SignatureType signatureType = determineSignatureType(out); |
| |
| final List<String> contentReferenceUris = getContentReferenceUris(out, signatureType, node); |
| |
| Node lastParent = null; |
| // per content reference URI a signature is built; for enveloped and enveloping there is only one content reference URI; |
| // only in the detached case there can be several |
| for (final String contentReferenceUri : contentReferenceUris) { |
| |
| // the method KeyAccessor.getKeyInfo must be called after the method KeyAccessor.getKeySelector, this is part of the interface contract! |
| // and this method must be called within the loop over the content reference URIs, because for each signature the key info ID must be different |
| final KeyInfo keyInfo = getConfiguration().getKeyAccessor().getKeyInfo(out, node, fac.getKeyInfoFactory()); |
| |
| String signatureId = getConfiguration().getSignatureId(); |
| if (signatureId == null) { |
| signatureId = "_" + UUID.randomUUID().toString(); |
| } else if (signatureId.isEmpty()) { |
| // indicator that no signature Id attribute shall be generated |
| signatureId = null; |
| } |
| |
| // parent only relevant for enveloped or detached signature |
| Node parent = getParentOfSignature(out, node, contentReferenceUri, signatureType); |
| |
| if (parent == null) { |
| // for enveloping signature, create new document |
| parent = XmlSignatureHelper.newDocumentBuilder(Boolean.TRUE).newDocument(); |
| } |
| lastParent = parent; |
| |
| XmlSignatureProperties.Input input = new InputBuilder().contentDigestAlgorithm(getDigestAlgorithmUri()).keyInfo(keyInfo) |
| .message(out).messageBodyNode(node).parent(parent).signatureAlgorithm(getConfiguration().getSignatureAlgorithm()) |
| .signatureFactory(fac).signatureId(signatureId).contentReferenceUri(contentReferenceUri) |
| .signatureType(signatureType) |
| .prefixForXmlSignatureNamespace(getConfiguration().getPrefixForXmlSignatureNamespace()).build(); |
| |
| XmlSignatureProperties.Output properties = getSignatureProperties(input); |
| |
| |
| // the signature properties can overwrite the signature Id |
| if (properties != null && properties.getSignatureId() != null && !properties.getSignatureId().isEmpty()) { |
| signatureId = properties.getSignatureId(); |
| } |
| |
| List<? extends XMLObject> objects = getObjects(input, properties); |
| List<? extends Reference> refs = getReferences(input, properties, getKeyInfoId(keyInfo)); |
| |
| SignedInfo si = createSignedInfo(fac, refs); |
| |
| DOMSignContext dsc = createAndConfigureSignContext(parent, keySelector); |
| |
| XMLSignature signature = fac.newXMLSignature(si, keyInfo, objects, signatureId, null); |
| // generate the signature |
| signature.sign(dsc); |
| } |
| |
| return XmlSignatureHelper.getDocument(lastParent); |
| |
| } catch (XMLSignatureException se) { |
| if (se.getCause() instanceof InvalidKeyException) { |
| throw new XmlSignatureInvalidKeyException(se.getMessage(), se); |
| } else { |
| throw new XmlSignatureException(se); |
| } |
| } catch (GeneralSecurityException e) { |
| // like NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchProviderException |
| throw new XmlSignatureException(e); |
| } |
| |
| } |
| |
| private SignatureType determineSignatureType(Message message) throws XmlSignatureException { |
| if (getConfiguration().getParentLocalName() != null && getConfiguration().getParentXpath() != null) { |
| throw new XmlSignatureException( |
| "The configuration of the XML signer component is wrong. The parent local name " |
| + getConfiguration().getParentLocalName() |
| + " and the parent XPath " + getConfiguration().getParentXpath().getXPath() + " are specified. You must not specify both parameters."); |
| |
| } |
| |
| boolean isEnveloped = getConfiguration().getParentLocalName() != null || getConfiguration().getParentXpath() != null; |
| |
| boolean isDetached = getXpathToIdAttributes(message).size() > 0; |
| |
| if (isEnveloped && isDetached) { |
| if (getConfiguration().getParentLocalName() != null) { |
| throw new XmlSignatureException( |
| "The configuration of the XML signer component is wrong. The parent local name " |
| + getConfiguration().getParentLocalName() |
| + " for an enveloped signature and the XPATHs to ID attributes for a detached signature are specified. You must not specify both parameters."); |
| } else { |
| throw new XmlSignatureException( |
| "The configuration of the XML signer component is wrong. The parent XPath " |
| + getConfiguration().getParentXpath().getXPath() |
| + " for an enveloped signature and the XPATHs to ID attributes for a detached signature are specified. You must not specify both parameters."); |
| |
| } |
| } |
| |
| SignatureType result; |
| if (isEnveloped) { |
| result = SignatureType.enveloped; |
| } else if (isDetached) { |
| if (getSchemaResourceUri(message) == null) { |
| throw new XmlSignatureException( |
| "The configruation of the XML Signature component is wrong: No XML schema specified in the detached case"); |
| } |
| result = SignatureType.detached; |
| } else { |
| result = SignatureType.enveloping; |
| } |
| |
| LOG.debug("Signature type: {}", result); |
| return result; |
| |
| } |
| |
| protected List<XPathFilterParameterSpec> getXpathToIdAttributes(Message message) { |
| |
| @SuppressWarnings("unchecked") |
| List<XPathFilterParameterSpec> result = (List<XPathFilterParameterSpec>) message |
| .getHeader(XmlSignatureConstants.HEADER_XPATHS_TO_ID_ATTRIBUTES); |
| if (result == null) { |
| result = getConfiguration().getXpathsToIdAttributes(); |
| } |
| return result; |
| } |
| |
| protected XmlSignatureProperties.Output getSignatureProperties(XmlSignatureProperties.Input input) throws Exception { //NOPMD |
| XmlSignatureProperties propGetter = getConfiguration().getProperties(); |
| XmlSignatureProperties.Output propsOutput = null; |
| if (propGetter != null) { |
| propsOutput = propGetter.get(input); |
| } |
| return propsOutput; |
| } |
| |
| private DOMSignContext createAndConfigureSignContext(Node parent, KeySelector keySelector) { |
| DOMSignContext dsc = new DOMSignContext(keySelector, parent); |
| // set namespace prefix for "http://www.w3.org/2000/09/xmldsig#" according to best practice described in http://www.w3.org/TR/xmldsig-bestpractices/#signing-xml-without-namespaces |
| if (getConfiguration().getPrefixForXmlSignatureNamespace() != null |
| && !getConfiguration().getPrefixForXmlSignatureNamespace().isEmpty()) { |
| dsc.putNamespacePrefix("http://www.w3.org/2000/09/xmldsig#", getConfiguration().getPrefixForXmlSignatureNamespace()); |
| } |
| dsc.putNamespacePrefix("http://www.w3.org/2001/10/xml-exc-c14n#", "ec"); |
| setCryptoContextProperties(dsc); |
| setUriDereferencerAndBaseUri(dsc); |
| return dsc; |
| } |
| |
| protected Boolean omitXmlDeclaration(Message message) { |
| Boolean omitXmlDeclaration = message.getHeader(XmlSignatureConstants.HEADER_OMIT_XML_DECLARATION, Boolean.class); |
| if (omitXmlDeclaration == null) { |
| omitXmlDeclaration = getConfiguration().getOmitXmlDeclaration(); |
| } |
| if (omitXmlDeclaration == null) { |
| omitXmlDeclaration = Boolean.FALSE; |
| } |
| LOG.debug("Omit XML declaration: {}", omitXmlDeclaration); |
| return omitXmlDeclaration; |
| } |
| |
| protected SignedInfo createSignedInfo(XMLSignatureFactory fac, List<? extends Reference> refs) throws Exception { //NOPMD |
| return fac.newSignedInfo(fac.newCanonicalizationMethod(getConfiguration().getCanonicalizationMethod().getAlgorithm(), |
| (C14NMethodParameterSpec) getConfiguration().getCanonicalizationMethod().getParameterSpec()), |
| getSignatureMethod(getConfiguration().getSignatureAlgorithm(), fac), refs); |
| } |
| |
| private SignatureMethod getSignatureMethod(String signatureAlgorithm, XMLSignatureFactory fac) throws NoSuchAlgorithmException, |
| InvalidAlgorithmParameterException { |
| return fac.newSignatureMethod(signatureAlgorithm, null); |
| } |
| |
| protected Node getMessageBodyNode(Message message) throws Exception { //NOPMD |
| InputStream is = message.getMandatoryBody(InputStream.class); |
| |
| Boolean isPlainText = isPlainText(message); |
| |
| Node node; |
| if (isPlainText != null && isPlainText) { |
| node = getTextNode(message, is); |
| } else { |
| ValidatorErrorHandler errorHandler = new DefaultValidationErrorHandler(); |
| Schema schema = getSchemaForSigner(message, errorHandler); |
| Document doc = parseInput(is, getConfiguration().getDisallowDoctypeDecl(), schema, errorHandler); |
| errorHandler.handleErrors(message.getExchange(), schema, null); // throws ValidationException |
| node = doc.getDocumentElement(); |
| LOG.debug("Root element of document to be signed: {}", node); |
| } |
| return node; |
| } |
| |
| protected Schema getSchemaForSigner(Message message, ValidatorErrorHandler errorHandler) throws XmlSignatureException, SAXException, |
| IOException { |
| Schema schema; |
| String schemaResourceUri = getSchemaResourceUri(message); |
| if (schemaResourceUri == null) { |
| schema = null; |
| } else { |
| schema = getSchema(message); |
| } |
| return schema; |
| } |
| |
| protected Boolean isPlainText(Message message) { |
| Boolean isPlainText = message.getHeader(XmlSignatureConstants.HEADER_MESSAGE_IS_PLAIN_TEXT, Boolean.class); |
| if (isPlainText == null) { |
| isPlainText = getConfiguration().getPlainText(); |
| } |
| LOG.debug("Is plain text: {}", isPlainText); |
| return isPlainText; |
| } |
| |
| protected Element getParentOfSignature(Message inMessage, Node messageBodyNode, String contentReferenceURI, SignatureType sigType) |
| throws Exception { //NOPMD |
| if (SignatureType.enveloping == sigType) { |
| // enveloping case |
| return null; |
| } |
| if (messageBodyNode.getParentNode() == null || messageBodyNode.getParentNode().getNodeType() != Node.DOCUMENT_NODE) { |
| throw new XmlSignatureFormatException( |
| "Incomming message has wrong format: It is not an XML document. Cannot create an enveloped or detached XML signature."); |
| } |
| Document doc = (Document) messageBodyNode.getParentNode(); |
| if (SignatureType.detached == sigType) { |
| return getParentForDetachedCase(doc, inMessage, contentReferenceURI); |
| } else { |
| // enveloped case |
| return getParentForEnvelopedCase(doc, inMessage); |
| } |
| |
| } |
| |
| protected Element getParentForEnvelopedCase(Document doc, Message inMessage) throws Exception { //NOPMD |
| if (getConfiguration().getParentXpath() != null) { |
| XPathFilterParameterSpec xp = getConfiguration().getParentXpath(); |
| XPathExpression exp; |
| try { |
| exp = XmlSignatureHelper.getXPathExpression(xp); |
| } catch (XPathExpressionException e) { |
| throw new XmlSignatureException("The parent XPath " + getConfiguration().getParentXpath().getXPath() + " is wrongly configured: The XPath " + xp.getXPath() + " is invalid.", e); |
| } |
| NodeList list = (NodeList) exp.evaluate(doc.getDocumentElement(), XPathConstants.NODESET); |
| if (list == null || list.getLength() == 0) { |
| throw new XmlSignatureException("The parent XPath " + xp.getXPath() + " returned no result. Check the configuration of the XML signer component."); |
| } |
| int length = list.getLength(); |
| for (int i = 0; i < length; i++) { |
| Node node = list.item(i); |
| if (node.getNodeType() == Node.ELEMENT_NODE) { |
| // return the first element |
| return (Element)node; |
| } |
| } |
| throw new XmlSignatureException("The parent XPath " + xp.getXPath() + " returned no element. Check the configuration of the XML signer component."); |
| } else { |
| // parent local name is not null! |
| NodeList parents = doc.getElementsByTagNameNS(getConfiguration().getParentNamespace(), getConfiguration().getParentLocalName()); |
| if (parents == null || parents.getLength() == 0) { |
| throw new XmlSignatureFormatException( |
| String.format( |
| "Incoming message has wrong format: The parent element with the local name %s and the namespace %s was not found in the message to build an enveloped XML signature.", |
| getConfiguration().getParentLocalName(), getConfiguration().getParentNamespace())); |
| } |
| // return the first element |
| return (Element) parents.item(0); |
| } |
| } |
| |
| private Element getParentForDetachedCase(Document doc, Message inMessage, String referenceUri) throws XmlSignatureException { |
| String elementId = referenceUri; |
| if (elementId.startsWith("#")) { |
| elementId = elementId.substring(1); |
| } |
| Element el = doc.getElementById(elementId); |
| if (el == null) { |
| // should not happen because has been checked before |
| throw new IllegalStateException("No element found for element ID " + elementId); |
| } |
| LOG.debug("Sibling element of the detached XML Signature with reference URI {}: {} {}", |
| new Object[] {referenceUri, el.getLocalName(), el.getNamespaceURI() }); |
| Element result = getParentElement(el); |
| if (result != null) { |
| return result; |
| } else { |
| throw new XmlSignatureException( |
| "Either the configuration of the XML Signature component is wrong or the incoming document has an invalid structure: The element " |
| + el.getLocalName() + "{" + el.getNamespaceURI() + "} which is referenced by the reference URI " + referenceUri |
| + " has no parent element. The element must have a parent element in the configured detached case."); |
| } |
| } |
| |
| private Element getParentElement(Node node) { |
| int counter = 0; |
| while (node != null && counter < 10000) { |
| // counter is for avoiding security attacks |
| Node parent = node.getParentNode(); |
| if (parent != null && parent.getNodeType() == Node.ELEMENT_NODE) { |
| return (Element) parent; |
| } |
| node = parent; |
| counter++; |
| } |
| return null; |
| } |
| |
| protected List<? extends Reference> getReferences(XmlSignatureProperties.Input input, XmlSignatureProperties.Output properties, |
| String keyInfoId) throws Exception { //NOPMD |
| |
| String referenceId = properties == null ? null : properties.getContentReferenceId(); |
| // Create Reference with URI="#<objectId>" for enveloping signature, URI="" for enveloped signature, and URI = <value from configuration> for detached signature and the transforms |
| Reference ref = createReference(input.getSignatureFactory(), input.getContentReferenceUri(), |
| getContentReferenceType(input.getMessage()), input.getSignatureType(), referenceId, input.getMessage()); |
| Reference keyInfoRef = createKeyInfoReference(input.getSignatureFactory(), keyInfoId, input.getContentDigestAlgorithm()); |
| |
| int propsRefsSize = properties == null || properties.getReferences() == null || properties.getReferences().isEmpty() ? 0 |
| : properties.getReferences().size(); |
| int size = keyInfoRef == null ? propsRefsSize + 1 : propsRefsSize + 2; |
| List<Reference> referenceList = new ArrayList<>(size); |
| referenceList.add(ref); |
| if (keyInfoRef != null) { |
| referenceList.add(keyInfoRef); |
| } |
| if (properties != null && properties.getReferences() != null && !properties.getReferences().isEmpty()) { |
| referenceList.addAll(properties.getReferences()); |
| } |
| return referenceList; |
| } |
| |
| protected List<? extends XMLObject> getObjects(XmlSignatureProperties.Input input, XmlSignatureProperties.Output properties) |
| throws Exception { //NOPMD |
| |
| if (SignatureType.enveloped == input.getSignatureType() || SignatureType.detached == input.getSignatureType()) { |
| if (properties == null || properties.getObjects() == null) { |
| return Collections.emptyList(); |
| } |
| return properties.getObjects(); |
| } |
| |
| // enveloping signature --> add additional object |
| final String objectId = getConfiguration().getContentObjectId(); |
| LOG.debug("Object Content Id {}", objectId); |
| |
| XMLObject obj = createXMLObject(input.getSignatureFactory(), input.getMessageBodyNode(), objectId); |
| if (properties == null || properties.getObjects() == null || properties.getObjects().isEmpty()) { |
| return Collections.singletonList(obj); |
| } |
| List<XMLObject> result = new ArrayList<>(properties.getObjects().size() + 1); |
| result.add(obj); |
| result.addAll(properties.getObjects()); |
| return result; |
| } |
| |
| private Node getTextNode(Message inMessage, InputStream is) throws IOException, ParserConfigurationException, XmlSignatureException { |
| LOG.debug("Message body to be signed is plain text"); |
| String encoding = getMessageEncoding(inMessage); |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
| IOHelper.copyAndCloseInput(is, bos); |
| try { |
| String text = new String(bos.toByteArray(), encoding); |
| return XmlSignatureHelper.newDocumentBuilder(true).newDocument().createTextNode(text); |
| } catch (UnsupportedEncodingException e) { |
| throw new XmlSignatureException(String.format("The message encoding %s is not supported.", encoding), e); |
| } |
| } |
| |
| protected String getMessageEncoding(Message inMessage) { |
| String encoding = inMessage.getHeader(XmlSignatureConstants.HEADER_PLAIN_TEXT_ENCODING, String.class); |
| if (encoding == null) { |
| encoding = getConfiguration().getPlainTextEncoding(); |
| } |
| LOG.debug("Messge encoding: {}", encoding); |
| return encoding; |
| } |
| |
| protected Document parseInput(InputStream is, Boolean disallowDoctypeDecl, Schema schema, ErrorHandler errorHandler) |
| throws ParserConfigurationException, IOException, XmlSignatureFormatException { |
| try { |
| DocumentBuilder db = XmlSignatureHelper.newDocumentBuilder(disallowDoctypeDecl, schema); |
| db.setErrorHandler(errorHandler); |
| return db.parse(is); |
| } catch (SAXException e) { |
| throw new XmlSignatureFormatException( |
| "XML signature generation not possible. Sent message is not an XML document. Check the sent message.", e); |
| } finally { |
| IOHelper.close(is, "input stream"); |
| } |
| } |
| |
| protected Reference createReference(XMLSignatureFactory fac, String uri, String type, SignatureType sigType, String id, Message message) |
| throws InvalidAlgorithmParameterException, XmlSignatureException { |
| try { |
| List<Transform> transforms = getTransforms(fac, sigType, message); |
| Reference ref = fac.newReference(uri, fac.newDigestMethod(getDigestAlgorithmUri(), null), transforms, type, id); |
| return ref; |
| } catch (NoSuchAlgorithmException e) { |
| throw new XmlSignatureException("Wrong algorithm specified in the configuration.", e); |
| } |
| } |
| |
| protected String getContentReferenceType(Message message) { |
| String type = message.getHeader(XmlSignatureConstants.HEADER_CONTENT_REFERENCE_TYPE, String.class); |
| if (type == null) { |
| type = getConfiguration().getContentReferenceType(); |
| } |
| LOG.debug("Content reference type: {}", type); |
| return type; |
| } |
| |
| protected List<String> getContentReferenceUris(Message message, SignatureType signatureType, Node messageBodyNode) |
| throws XmlSignatureException, XPathExpressionException { |
| |
| List<String> result; |
| if (SignatureType.enveloping == signatureType) { |
| String uri = "#" + getConfiguration().getContentObjectId(); |
| result = Collections.singletonList(uri); |
| } else if (SignatureType.enveloped == signatureType) { |
| // only for enveloped the parameter content reference URI is used |
| String uri = message.getHeader(XmlSignatureConstants.HEADER_CONTENT_REFERENCE_URI, String.class); |
| if (uri == null) { |
| uri = getConfiguration().getContentReferenceUri(); |
| } |
| if (uri == null) { |
| uri = ""; |
| } |
| result = Collections.singletonList(uri); |
| } else if (SignatureType.detached == signatureType) { |
| result = getContentReferenceUrisForDetachedCase(message, messageBodyNode); |
| } else { |
| // should not occur |
| throw new IllegalStateException("Signature type " + signatureType + " not supported"); |
| } |
| |
| LOG.debug("Content reference URI(s): {}", result); |
| return result; |
| } |
| |
| private List<String> getContentReferenceUrisForDetachedCase(Message message, Node messageBodyNode) throws XmlSignatureException, |
| XPathExpressionException { |
| List<XPathFilterParameterSpec> xpathsToIdAttributes = getXpathToIdAttributes(message); |
| if (xpathsToIdAttributes.isEmpty()) { |
| // should not happen, has already been checked earlier |
| throw new IllegalStateException("List of XPATHs to ID attributes is empty in detached signature case"); |
| } |
| List<ComparableNode> result = new ArrayList<>(xpathsToIdAttributes.size()); |
| for (XPathFilterParameterSpec xp : xpathsToIdAttributes) { |
| XPathExpression exp; |
| try { |
| exp = XmlSignatureHelper.getXPathExpression(xp); |
| } catch (XPathExpressionException e) { |
| throw new XmlSignatureException("The configured xpath expression " + xp.getXPath() + " is invalid.", e); |
| } |
| NodeList list = (NodeList) exp.evaluate(messageBodyNode, XPathConstants.NODESET); |
| if (list == null) { |
| //assume optional element, XSD validation has been done before |
| LOG.warn("No ID attribute found for xpath expression {}. Therfore this xpath expression will be ignored.", xp.getXPath()); |
| continue; |
| } |
| int length = list.getLength(); |
| for (int i = 0; i < length; i++) { |
| Node node = list.item(i); |
| if (node.getNodeType() == Node.ATTRIBUTE_NODE) { |
| Attr attr = (Attr) node; |
| String value = attr.getValue(); |
| // check that attribute is ID attribute |
| Element element = messageBodyNode.getOwnerDocument().getElementById(value); |
| if (element == null) { |
| throw new XmlSignatureException( |
| "Wrong configured xpath expression for ID attributes: The evaluation of the xpath expression " |
| + xp.getXPath() + " resulted in an attribute which is not of type ID. The attribute value is " |
| + value + "."); |
| } |
| result.add(new ComparableNode(element, "#" + value)); |
| LOG.debug("ID attribute with value {} found for xpath {}", value, xp.getXPath()); |
| } else { |
| throw new XmlSignatureException( |
| "Wrong configured xpath expression for ID attributes: The evaluation of the xpath expression " + xp.getXPath() |
| + " returned a node which was not of type Attribute."); |
| } |
| } |
| } |
| if (result.size() == 0) { |
| throw new XmlSignatureException( |
| "No element to sign found in the detached case. No node found for the configured xpath expressions " |
| + toString(xpathsToIdAttributes) |
| + ". Either the configuration of the XML signature component is wrong or the incoming message has not the correct structure."); |
| } |
| // sort so that elements with deeper hierarchy level are treated first |
| Collections.sort(result); |
| return ComparableNode.getReferenceUris(result); |
| } |
| |
| private String toString(List<XPathFilterParameterSpec> xpathsToIdAttributes) { |
| StringBuilder result = new StringBuilder(); |
| int counter = 0; |
| for (XPathFilterParameterSpec xp : xpathsToIdAttributes) { |
| counter++; |
| result.append(xp.getXPath()); |
| if (counter < xpathsToIdAttributes.size()) { |
| result.append(", "); |
| } |
| } |
| return result.toString(); |
| } |
| |
| protected XMLObject createXMLObject(XMLSignatureFactory fac, Node node, String id) { |
| return fac.newXMLObject(Collections.singletonList(new DOMStructure(node)), id, null, null); |
| } |
| |
| private List<Transform> getTransforms(XMLSignatureFactory fac, SignatureType sigType, Message message) throws NoSuchAlgorithmException, |
| InvalidAlgorithmParameterException { |
| String transformMethodsHeaderValue = message.getHeader(XmlSignatureConstants.HEADER_TRANSFORM_METHODS, String.class); |
| if (transformMethodsHeaderValue == null) { |
| List<AlgorithmMethod> configuredTrafos = getConfiguration().getTransformMethods(); |
| if (SignatureType.enveloped == sigType) { |
| // add enveloped transform if necessary |
| if (configuredTrafos.size() > 0) { |
| if (!containsEnvelopedTransform(configuredTrafos)) { |
| configuredTrafos = new ArrayList<>(configuredTrafos.size() + 1); |
| configuredTrafos.add(XmlSignatureHelper.getEnvelopedTransform()); |
| configuredTrafos.addAll(getConfiguration().getTransformMethods()); |
| } |
| } else { |
| // add enveloped and C14N trafo |
| configuredTrafos = new ArrayList<>(2); |
| configuredTrafos.add(XmlSignatureHelper.getEnvelopedTransform()); |
| configuredTrafos.add(XmlSignatureHelper.getCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE)); |
| } |
| } |
| |
| List<Transform> transforms = new ArrayList<>(configuredTrafos.size()); |
| for (AlgorithmMethod trafo : configuredTrafos) { |
| Transform transform = fac.newTransform(trafo.getAlgorithm(), (TransformParameterSpec) trafo.getParameterSpec()); |
| transforms.add(transform); |
| LOG.debug("Transform method: {}", trafo.getAlgorithm()); |
| } |
| return transforms; |
| } else { |
| LOG.debug("Header {} with value '{}' found", XmlSignatureConstants.HEADER_TRANSFORM_METHODS, transformMethodsHeaderValue); |
| String[] transformAlgorithms = transformMethodsHeaderValue.split(","); |
| List<Transform> transforms = new ArrayList<>(transformAlgorithms.length); |
| for (String transformAlgorithm : transformAlgorithms) { |
| transformAlgorithm = transformAlgorithm.trim(); |
| Transform transform = fac.newTransform(transformAlgorithm, (TransformParameterSpec) null); |
| transforms.add(transform); |
| LOG.debug("Transform method: {}", transformAlgorithm); |
| } |
| return transforms; |
| } |
| } |
| |
| private boolean containsEnvelopedTransform(List<AlgorithmMethod> configuredTrafos) { |
| for (AlgorithmMethod m : configuredTrafos) { |
| if (Transform.ENVELOPED.equals(m.getAlgorithm())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| protected String getDigestAlgorithmUri() throws XmlSignatureException { |
| |
| String result = getConfiguration().getDigestAlgorithm(); |
| if (result == null) { |
| String signatureAlgorithm = getConfiguration().getSignatureAlgorithm(); |
| if (signatureAlgorithm != null) { |
| if (signatureAlgorithm.contains(SHA1)) { |
| result = DigestMethod.SHA1; |
| } else if (signatureAlgorithm.contains(SHA224)) { |
| result = HTTP_WWW_W3_ORG_2001_04_XMLDSIG_MORE_SHA224; |
| } else if (signatureAlgorithm.contains(SHA256)) { |
| result = DigestMethod.SHA256; |
| } else if (signatureAlgorithm.contains(SHA384)) { |
| result = HTTP_WWW_W3_ORG_2001_04_XMLDSIG_MORE_SHA384; |
| } else if (signatureAlgorithm.contains(SHA512)) { |
| result = DigestMethod.SHA512; |
| } else if (signatureAlgorithm.contains(RIPEMD160)) { |
| return DigestMethod.RIPEMD160; |
| } |
| } |
| } |
| if (result != null) { |
| LOG.debug("Digest algorithm: {}", result); |
| return result; |
| } |
| throw new XmlSignatureException( |
| "Digest algorithm missing for XML signature generation. Specify the digest algorithm in the configuration."); |
| } |
| |
| protected Reference createKeyInfoReference(XMLSignatureFactory fac, String keyInfoId, String digestAlgorithm) throws Exception { //NOPMD |
| |
| if (keyInfoId == null) { |
| return null; |
| } |
| if (getConfiguration().getAddKeyInfoReference() == null) { |
| return null; |
| } |
| |
| if (!getConfiguration().getAddKeyInfoReference()) { |
| return null; |
| } |
| |
| LOG.debug("Creating reference to key info element with Id: {}", keyInfoId); |
| List<Transform> transforms = new ArrayList<>(1); |
| Transform transform = fac.newTransform(CanonicalizationMethod.INCLUSIVE, (TransformParameterSpec) null); |
| transforms.add(transform); |
| return fac.newReference("#" + keyInfoId, fac.newDigestMethod(digestAlgorithm, null), transforms, null, null); |
| } |
| |
| private String getKeyInfoId(KeyInfo keyInfo) throws Exception { //NOPMD |
| if (keyInfo == null) { |
| return null; |
| } |
| return keyInfo.getId(); |
| } |
| |
| protected void setOutputEncodingToMessageHeader(Message message) { |
| if (getConfiguration().getOutputXmlEncoding() != null) { |
| message.setHeader(Exchange.CHARSET_NAME, getConfiguration().getOutputXmlEncoding()); |
| } |
| } |
| |
| |
| private static class InputBuilder { |
| |
| private XMLSignatureFactory signatureFactory; |
| |
| private String signatureAlgorithm; |
| |
| private Node parent; |
| |
| private Node messageBodyNode; |
| |
| private Message message; |
| |
| private KeyInfo keyInfo; |
| |
| private String contentDigestAlgorithm; |
| |
| private String signatureId; |
| |
| private String contentReferenceUri; |
| |
| private SignatureType signatureType; |
| |
| private String prefixForXmlSignatureNamespace; |
| |
| public InputBuilder signatureFactory(XMLSignatureFactory signatureFactory) { |
| this.signatureFactory = signatureFactory; |
| return this; |
| } |
| |
| public InputBuilder signatureAlgorithm(String signatureAlgorithm) { |
| this.signatureAlgorithm = signatureAlgorithm; |
| return this; |
| } |
| |
| public InputBuilder parent(Node parent) { |
| this.parent = parent; |
| return this; |
| } |
| |
| public InputBuilder messageBodyNode(Node messageBodyNode) { |
| this.messageBodyNode = messageBodyNode; |
| return this; |
| } |
| |
| public InputBuilder message(Message message) { |
| this.message = message; |
| return this; |
| } |
| |
| public InputBuilder keyInfo(KeyInfo keyInfo) { |
| this.keyInfo = keyInfo; |
| return this; |
| } |
| |
| public InputBuilder contentDigestAlgorithm(String contentDigestAlgorithm) { |
| this.contentDigestAlgorithm = contentDigestAlgorithm; |
| return this; |
| } |
| |
| public InputBuilder signatureId(String signatureId) { |
| this.signatureId = signatureId; |
| return this; |
| } |
| |
| public InputBuilder contentReferenceUri(String contentReferenceUri) { |
| this.contentReferenceUri = contentReferenceUri; |
| return this; |
| } |
| |
| public InputBuilder signatureType(SignatureType signatureType) { |
| this.signatureType = signatureType; |
| return this; |
| } |
| |
| public InputBuilder prefixForXmlSignatureNamespace(String prefixForXmlSignatureNamespace) { |
| this.prefixForXmlSignatureNamespace = prefixForXmlSignatureNamespace; |
| return this; |
| } |
| |
| public XmlSignatureProperties.Input build() { |
| return new XmlSignatureProperties.Input() { |
| |
| @Override |
| public XMLSignatureFactory getSignatureFactory() { |
| return signatureFactory; |
| } |
| |
| @Override |
| public String getSignatureAlgorithm() { |
| return signatureAlgorithm; |
| } |
| |
| @Override |
| public Node getParent() { |
| return parent; |
| } |
| |
| @Override |
| public Node getMessageBodyNode() { |
| return messageBodyNode; |
| } |
| |
| @Override |
| public Message getMessage() { |
| return message; |
| } |
| |
| @Override |
| public KeyInfo getKeyInfo() { |
| return keyInfo; |
| } |
| |
| @Override |
| public String getContentDigestAlgorithm() { |
| return contentDigestAlgorithm; |
| } |
| |
| @Override |
| public String getSignatureId() { |
| return signatureId; |
| } |
| |
| @Override |
| public String getContentReferenceUri() { |
| return contentReferenceUri; |
| } |
| |
| @Override |
| public SignatureType getSignatureType() { |
| return signatureType; |
| } |
| |
| @Override |
| public String getPrefixForXmlSignatureNamespace() { |
| return prefixForXmlSignatureNamespace; |
| } |
| |
| }; |
| } |
| |
| } |
| |
| /** Compares nodes by their hierarchy level. */ |
| static class ComparableNode implements Comparable<ComparableNode> { |
| |
| private final String referenceUri; |
| private final int level; |
| |
| ComparableNode(Element node, String referenceUri) { |
| this.referenceUri = referenceUri; |
| level = calculateLevel(node); |
| } |
| |
| private int calculateLevel(Element node) { |
| int counter = 0; |
| for (Node n = node; n != null; n = n.getParentNode()) { |
| if (Node.ELEMENT_NODE == n.getNodeType()) { |
| counter++; |
| if (counter > 10000) { |
| // prevent security attack |
| throw new IllegalStateException("Hierachy level is limited to 10000"); |
| } |
| } |
| } |
| return counter; |
| } |
| |
| @Override |
| public int compareTo(ComparableNode o) { |
| return o.level - level; |
| } |
| |
| String getReferenceUri() { |
| return referenceUri; |
| } |
| |
| static List<String> getReferenceUris(List<ComparableNode> input) { |
| List<String> result = new ArrayList<>(input.size()); |
| for (ComparableNode cn : input) { |
| result.add(cn.getReferenceUri()); |
| } |
| return result; |
| } |
| |
| } |
| |
| } |