/**
* 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.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
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.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) {
super();
this.config = config;
}
@Override
public XmlVerifierConfiguration getConfiguration() {
return config;
}
@Override
public void process(Exchange exchange) throws Exception {
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");
}
}
protected void verify(InputStream input, final Message out)
throws Exception {
LOG.debug("Verification of XML signature document started");
final Document doc = parseInput(input);
Node signatureNode = getSignatureNode(doc);
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, signatureNode);
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);
final XMLSignature signature = fac.unmarshalXMLSignature(valContext);
executeApplicationCheck(out, doc, signature);
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");
map2Message(signature, out, doc);
} else {
throw new XmlSignatureInvalidException("");
}
}
private void executeApplicationCheck(final Message out, final Document doc,
final XMLSignature signature) throws Exception {
if (getConfiguration().getXmlSignatureChecker() != null) {
XmlSignatureChecker.Input checkerInput = new XmlSignatureChecker.Input() {
@Override
public SignedInfo getSignedInfo() {
return signature.getSignedInfo();
}
@Override
public SignatureValue getSignatureValue() {
return signature.getSignatureValue();
}
@SuppressWarnings("unchecked")
@Override
public List<? extends XMLObject> getObjects() {
return (List<? extends XMLObject>) signature.getObjects();
}
@Override
public Document getMessageBodyDocument() {
return doc;
}
@Override
public Message getMessage() {
return out;
}
@Override
public KeyInfo getKeyInfo() {
return signature.getKeyInfo();
}
};
getConfiguration().getXmlSignatureChecker().checkBeforeCoreValidation(checkerInput);
}
}
private void map2Message(XMLSignature signature, Message out,
final Document messageBodyDocument) throws Exception {
@SuppressWarnings("unchecked")
final List<Reference> refs = new ArrayList<Reference>(signature.getSignedInfo().getReferences());
@SuppressWarnings("unchecked")
final List<XMLObject> objs = new ArrayList<XMLObject>(signature.getObjects());
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();
}
public Boolean getRemoveSignatureElements() {
return getConfiguration().getRemoveSignatureElements();
}
};
getConfiguration().getXmlSignature2Message().mapToMessage(refsAndObjects, out);
}
private Node getSignatureNode(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.");
}
if (nl.getLength() != 1) {
throw new XmlSignatureFormatException(
"XML signature document is not supported; it contains more than one signature element. Check the sent message.");
}
Node signatureNode = nl.item(0);
LOG.debug("Signature element found");
return signatureNode;
}
@SuppressWarnings("unchecked")
protected boolean handleSignatureValidationFailed(DOMValidateContext valContext,
XMLSignature signature) throws Exception {
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)
throws XmlSignatureFormatException, ParserConfigurationException,
IOException {
try {
Document doc =
XmlSignatureHelper.newDocumentBuilder(getConfiguration().getDisallowDoctypeDecl()).parse(is);
return doc;
} catch (SAXException e) {
throw new XmlSignatureFormatException(
"Message has wrong format, it is not a XML signature document. Check the sent message.",
e
);
}
}
}