/**
* 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((List<XMLObject>) signature.getObjects());
collectedReferences.addAll((List<Reference>) 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;
}
};
}
}
}