/** * 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.IOException; import java.io.InputStream; import java.security.NoSuchProviderException; import java.util.ArrayList; import java.util.List; import javax.xml.crypto.KeySelector; import javax.xml.crypto.XMLStructure; import javax.xml.crypto.dsig.Manifest; import javax.xml.crypto.dsig.Reference; import javax.xml.crypto.dsig.SignedInfo; import javax.xml.crypto.dsig.XMLObject; import javax.xml.crypto.dsig.XMLSignature; import javax.xml.crypto.dsig.XMLSignature.SignatureValue; import javax.xml.crypto.dsig.XMLSignatureException; import javax.xml.crypto.dsig.XMLSignatureFactory; import javax.xml.crypto.dsig.dom.DOMValidateContext; import javax.xml.crypto.dsig.keyinfo.KeyInfo; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.ParserConfigurationException; import javax.xml.validation.Schema; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import org.apache.camel.Exchange; import org.apache.camel.Message; 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.XmlSignatureFormatException; import org.apache.camel.component.xmlsecurity.api.XmlSignatureHelper; import org.apache.camel.component.xmlsecurity.api.XmlSignatureInvalidException; import org.apache.camel.processor.validation.DefaultValidationErrorHandler; import org.apache.camel.processor.validation.ValidatorErrorHandler; import org.apache.camel.util.IOHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * XML signature verifier. Assumes that the input XML contains exactly one * Signature element. */ public class XmlVerifierProcessor extends XmlSignatureProcessor { private static final Logger LOG = LoggerFactory.getLogger(XmlVerifierProcessor.class); private final XmlVerifierConfiguration config; public XmlVerifierProcessor(XmlVerifierConfiguration config) { this.config = config; } @Override public XmlVerifierConfiguration getConfiguration() { return config; } @Override public void process(Exchange exchange) throws Exception { //NOPMD InputStream stream = exchange.getIn().getMandatoryBody(InputStream.class); try { // 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()); verify(stream, out); clearMessageHeaders(out); } catch (Exception e) { // remove OUT message, as an exception occurred exchange.setOut(null); throw e; } finally { IOHelper.close(stream, "input stream"); } } @SuppressWarnings("unchecked") protected void verify(InputStream input, final Message out) throws Exception { //NOPMD LOG.debug("Verification of XML signature document started"); final Document doc = parseInput(input, out); 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"); } KeySelector selector = getConfiguration().getKeySelector(); if (selector == null) { throw new IllegalStateException("Wrong configuration. Key selector is missing."); } DOMValidateContext valContext = new DOMValidateContext(selector, doc); valContext.setProperty("javax.xml.crypto.dsig.cacheReference", Boolean.TRUE); valContext.setProperty("org.jcp.xml.dsig.validateManifests", Boolean.TRUE); if (getConfiguration().getSecureValidation() == Boolean.TRUE) { valContext.setProperty("org.apache.jcp.xml.dsig.secureValidation", Boolean.TRUE); valContext.setProperty("org.jcp.xml.dsig.secureValidation", Boolean.TRUE); } setUriDereferencerAndBaseUri(valContext); setCryptoContextProperties(valContext); NodeList signatureNodes = getSignatureNodes(doc); List<XMLObject> collectedObjects = new ArrayList<XMLObject>(3); List<Reference> collectedReferences = new ArrayList<Reference>(3); int totalCount = signatureNodes.getLength(); for (int i = 0; i < totalCount; i++) { Element signatureNode = (Element) signatureNodes.item(i); valContext.setNode(signatureNode); final XMLSignature signature = fac.unmarshalXMLSignature(valContext); if (getConfiguration().getXmlSignatureChecker() != null) { XmlSignatureChecker.Input checkerInput = new CheckerInputBuilder().message(out).messageBodyDocument(doc) .keyInfo(signature.getKeyInfo()).currentCountOfSignatures(i + 1).currentSignatureElement(signatureNode) .objects(signature.getObjects()).signatureValue(signature.getSignatureValue()) .signedInfo(signature.getSignedInfo()).totalCountOfSignatures(totalCount) .xmlSchemaValidationExecuted(getSchemaResourceUri(out) != null).build(); getConfiguration().getXmlSignatureChecker().checkBeforeCoreValidation(checkerInput); } boolean coreValidity; try { coreValidity = signature.validate(valContext); } catch (XMLSignatureException se) { throw getConfiguration().getValidationFailedHandler().onXMLSignatureException(se); } // Check core validation status boolean goon = coreValidity; if (!coreValidity) { goon = handleSignatureValidationFailed(valContext, signature); } if (goon) { LOG.debug("XML signature {} verified", i + 1); } else { throw new XmlSignatureInvalidException("XML signature validation failed"); } collectedObjects.addAll(signature.getObjects()); collectedReferences.addAll(signature.getSignedInfo().getReferences()); } map2Message(collectedReferences, collectedObjects, out, doc); } private void map2Message(final List<Reference> refs, final List<XMLObject> objs, Message out, final Document messageBodyDocument) throws Exception { //NOPMD XmlSignature2Message.Input refsAndObjects = new XmlSignature2Message.Input() { @Override public List<Reference> getReferences() { return refs; } @Override public List<XMLObject> getObjects() { return objs; } @Override public Document getMessageBodyDocument() { return messageBodyDocument; } @Override public Boolean omitXmlDeclaration() { return getConfiguration().getOmitXmlDeclaration(); } @Override public Object getOutputNodeSearch() { return getConfiguration().getOutputNodeSearch(); } @Override public String getOutputNodeSearchType() { return getConfiguration().getOutputNodeSearchType(); } @Override public Boolean getRemoveSignatureElements() { return getConfiguration().getRemoveSignatureElements(); } @Override public String getOutputXmlEncoding() { return getConfiguration().getOutputXmlEncoding(); } }; getConfiguration().getXmlSignature2Message().mapToMessage(refsAndObjects, out); } private NodeList getSignatureNodes(Document doc) throws IOException, ParserConfigurationException, XmlSignatureFormatException { // Find Signature element NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); if (nl.getLength() == 0) { throw new XmlSignatureFormatException( "Message is not a correct XML signature document: 'Signature' element is missing. Check the sent message."); } LOG.debug("{} signature elements found", nl.getLength()); return nl; } @SuppressWarnings("unchecked") protected boolean handleSignatureValidationFailed(DOMValidateContext valContext, XMLSignature signature) throws Exception { //NOPMD ValidationFailedHandler handler = getConfiguration().getValidationFailedHandler(); LOG.debug("handleSignatureValidationFailed called"); try { handler.start(); // first check signature value, see // https://www.isecpartners.com/media/12012/XMLDSIG_Command_Injection.pdf SignatureValue sigValue = signature.getSignatureValue(); boolean sv = sigValue.validate(valContext); if (!sv) { handler.signatureValueValidationFailed(sigValue); } // then the references! // check the validation status of each Reference for (Reference ref : (List<Reference>) signature.getSignedInfo().getReferences()) { boolean refValid = ref.validate(valContext); if (!refValid) { handler.referenceValidationFailed(ref); } } // validate Manifests, if property set if (Boolean.TRUE.equals(valContext.getProperty("org.jcp.xml.dsig.validateManifests"))) { for (XMLObject xo : (List<XMLObject>) signature.getObjects()) { List<XMLStructure> content = xo.getContent(); for (XMLStructure xs : content) { if (xs instanceof Manifest) { Manifest man = (Manifest) xs; for (Reference ref : (List<Reference>) man.getReferences()) { boolean refValid = ref.validate(valContext); if (!refValid) { handler.manifestReferenceValidationFailed(ref); } } } } } } boolean goon = handler.ignoreCoreValidationFailure(); LOG.debug("Ignore Core Validation failure: {}", goon); return goon; } finally { handler.end(); } } protected Document parseInput(InputStream is, Message message) throws Exception { //NOPMD try { ValidatorErrorHandler errorHandler = new DefaultValidationErrorHandler(); Schema schema = getSchema(message); DocumentBuilder db = XmlSignatureHelper.newDocumentBuilder(getConfiguration().getDisallowDoctypeDecl(), schema); db.setErrorHandler(errorHandler); Document doc = db.parse(is); errorHandler.handleErrors(message.getExchange(), schema, null); // throws ValidationException return doc; } catch (SAXException e) { throw new XmlSignatureFormatException("Message has wrong format, it is not a XML signature document. Check the sent message.", e); } } static class CheckerInputBuilder { private boolean xmlSchemaValidationExecuted; private int totalCountOfSignatures; private SignedInfo signedInfo; private SignatureValue signatureValue; private List<? extends XMLObject> objects; private Document messageBodyDocument; private Message message; private KeyInfo keyInfo; private Element currentSignatureElement; private int currentCountOfSignatures; CheckerInputBuilder xmlSchemaValidationExecuted(boolean xmlSchemaValidationExecuted) { this.xmlSchemaValidationExecuted = xmlSchemaValidationExecuted; return this; } CheckerInputBuilder totalCountOfSignatures(int totalCountOfSignatures) { this.totalCountOfSignatures = totalCountOfSignatures; return this; } CheckerInputBuilder signedInfo(SignedInfo signedInfo) { this.signedInfo = signedInfo; return this; } CheckerInputBuilder signatureValue(SignatureValue signatureValue) { this.signatureValue = signatureValue; return this; } CheckerInputBuilder objects(List<? extends XMLObject> objects) { this.objects = objects; return this; } CheckerInputBuilder messageBodyDocument(Document messageBodyDocument) { this.messageBodyDocument = messageBodyDocument; return this; } CheckerInputBuilder message(Message message) { this.message = message; return this; } CheckerInputBuilder keyInfo(KeyInfo keyInfo) { this.keyInfo = keyInfo; return this; } CheckerInputBuilder currentSignatureElement(Element currentSignatureElement) { this.currentSignatureElement = currentSignatureElement; return this; } CheckerInputBuilder currentCountOfSignatures(int currentCountOfSignatures) { this.currentCountOfSignatures = currentCountOfSignatures; return this; } XmlSignatureChecker.Input build() { return new XmlSignatureChecker.Input() { @Override public boolean isXmlSchemaValidationExecuted() { return xmlSchemaValidationExecuted; } @Override public int getTotalCountOfSignatures() { return totalCountOfSignatures; } @Override public SignedInfo getSignedInfo() { return signedInfo; } @Override public SignatureValue getSignatureValue() { return signatureValue; } @Override public List<? extends XMLObject> getObjects() { return objects; } @Override public Document getMessageBodyDocument() { return messageBodyDocument; } @Override public Message getMessage() { return message; } @Override public KeyInfo getKeyInfo() { return keyInfo; } @Override public Element getCurrentSignatureElement() { return currentSignatureElement; } @Override public int getCurrentCountOfSignatures() { return currentCountOfSignatures; } }; } } }