mirror of
https://github.com/itplr-kosit/validator.git
synced 2026-05-26 01:05:38 +00:00
(enhance) introduce resolving strategy (configurable xml security); introduce API configuration
This commit is contained in:
parent
7a86f049ac
commit
35c0797898
67 changed files with 2441 additions and 845 deletions
|
|
@ -0,0 +1,108 @@
|
|||
package de.kosit.validationtool.impl.xml;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.validation.SchemaFactory;
|
||||
import javax.xml.validation.Validator;
|
||||
|
||||
import org.xml.sax.SAXNotRecognizedException;
|
||||
import org.xml.sax.SAXNotSupportedException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import de.kosit.validationtool.api.ResolvingConfigurationStrategy;
|
||||
|
||||
/**
|
||||
* @author Andreas Penski
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class BaseResolvingStrategy implements ResolvingConfigurationStrategy {
|
||||
|
||||
protected static final String DISSALLOW_DOCTYPE_DECL_FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
|
||||
|
||||
protected static final String LOAD_EXTERNAL_DTD_FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
|
||||
|
||||
protected static final String FEATURE_SECURE_PROCESSING = "http://javax.xml.XMLConstants/feature/secure-processing";
|
||||
|
||||
private static final String ORACLE_XERCES_CLASS = "com.sun.org.apache.xerces.internal.impl.Constants";
|
||||
|
||||
public static void forceOpenJdkXmlImplementation() {
|
||||
if (!isOpenJdkXmlImplementationAvailable()) {
|
||||
throw new IllegalStateException("No OpenJDK version of XERCES found");
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isOpenJdkXmlImplementationAvailable() {
|
||||
try {
|
||||
Class.forName(ORACLE_XERCES_CLASS);
|
||||
return true;
|
||||
} catch (final ClassNotFoundException e) {
|
||||
log.warn("No oracle JDK version of XERCES found. Configured security features may not have any effect.");
|
||||
log.warn("Please take care of XML security while checking your xml contents");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void setProperty(final PropertySetter setter, final boolean lenient, final String errorMessage) {
|
||||
try {
|
||||
setter.apply();
|
||||
} catch (final SAXNotRecognizedException | SAXNotSupportedException e) {
|
||||
|
||||
if (lenient) {
|
||||
log.warn(errorMessage);
|
||||
log.debug(e.getMessage(), e);
|
||||
} else {
|
||||
throw new IllegalStateException(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void allowExternalSchema(final Validator validator, final String... scheme) {
|
||||
allowExternalSchema(validator, false, scheme);
|
||||
}
|
||||
|
||||
protected void allowExternalSchema(final SchemaFactory schemaFactory, final String... scheme) {
|
||||
allowExternalSchema(schemaFactory, false, scheme);
|
||||
}
|
||||
|
||||
protected void allowExternalSchema(final Validator validator, final boolean lenient, final String... schemes) {
|
||||
final String schemeString = String.join(",", schemes);
|
||||
setProperty(() -> validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, schemeString), lenient, format(
|
||||
"Can set external schema access to schemes (%s). Maybe an unsupported JAXP implementation is used.", schemeString));
|
||||
}
|
||||
|
||||
protected void allowExternalSchema(final SchemaFactory schemaFactory, final boolean lenient, final String... schemes) {
|
||||
final String schemeString = String.join(",", schemes);
|
||||
setProperty(() -> schemaFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, schemeString), lenient, format(
|
||||
"Can set external schema access to schemes (%s). Maybe an unsupported JAXP implementation is used.", schemeString));
|
||||
}
|
||||
|
||||
protected void disableExternalEntities(final Validator validator) {
|
||||
disableExternalEntities(validator, false);
|
||||
}
|
||||
|
||||
protected void disableExternalEntities(final SchemaFactory schemaFactory) {
|
||||
disableExternalEntities(schemaFactory, false);
|
||||
}
|
||||
|
||||
protected void disableExternalEntities(final Validator validator, final boolean lenient) {
|
||||
log.debug("Try to disable extern DTD access");
|
||||
setProperty(() -> validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""), lenient,
|
||||
"Can not disable external DTD access. Maybe an unsupported JAXP implementation is used.");
|
||||
|
||||
}
|
||||
|
||||
protected void disableExternalEntities(final SchemaFactory schemaFactory, final boolean lenient) {
|
||||
log.debug("Try to disable extern DTD access");
|
||||
setProperty(() -> schemaFactory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""), lenient,
|
||||
"Can not disable external DTD access. Maybe an unsupported JAXP implementation is used.");
|
||||
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface PropertySetter {
|
||||
|
||||
void apply() throws SAXNotRecognizedException, SAXNotSupportedException;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Licensed to the Koordinierungsstelle für IT-Standards (KoSIT) under
|
||||
* one or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. KoSIT 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 de.kosit.validationtool.impl.xml;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.URI;
|
||||
|
||||
import javax.xml.transform.Source;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.URIResolver;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import net.sf.saxon.Configuration;
|
||||
import net.sf.saxon.trans.XPathException;
|
||||
|
||||
/**
|
||||
* {@link URIResolver} that resolves artifacts relative to a given base uri. The resolved URI must be resolving as child
|
||||
* e.g. the baseUri must be a parent of the resolved artifact.
|
||||
*
|
||||
* @author Andreas Penski
|
||||
*/
|
||||
@RequiredArgsConstructor()
|
||||
public class RelativeUriResolver implements URIResolver {
|
||||
|
||||
/** the base uri */
|
||||
private final URI baseUri;
|
||||
|
||||
@Override
|
||||
public Source resolve(final String href, final String base) throws TransformerException {
|
||||
final URI resolved = resolve(URI.create(href), URI.create(base));
|
||||
if (isUnderBaseUri(resolved)) {
|
||||
try {
|
||||
return new StreamSource(resolved.toURL().openStream(), resolved.toASCIIString());
|
||||
} catch (final IOException e) {
|
||||
|
||||
throw new TransformerException(String.format("Can not resolve required %s", href), e);
|
||||
}
|
||||
} else {
|
||||
throw new TransformerException(String
|
||||
.format("The resolved transformation artifact %s is not within the configured repository %s", resolved, this.baseUri));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a relative uri including uris within a jar file.
|
||||
*
|
||||
* @param href the uri to resolve
|
||||
* @param base the base uri
|
||||
* @return the resolved uri
|
||||
*/
|
||||
public static URI resolve(final URI href, final URI base) {
|
||||
final boolean jarURI = isJarURI(base);
|
||||
final URI tmpBase = jarURI ? URI.create(base.toASCIIString().substring(4)) : base;
|
||||
final URI result = tmpBase.resolve(href);
|
||||
return jarURI ? URI.create("jar:" + result.toString()) : result;
|
||||
}
|
||||
|
||||
static boolean isJarURI(final URI uri) {
|
||||
return uri.isOpaque() && uri.getScheme().equals("jar");
|
||||
}
|
||||
|
||||
private boolean isUnderBaseUri(final URI resolved) {
|
||||
final String base = this.baseUri.toASCIIString().replaceAll("file:/+", "");
|
||||
final String r = resolved.toASCIIString().replaceAll("file:/+", "");
|
||||
return r.startsWith(base);
|
||||
}
|
||||
|
||||
|
||||
public Reader resolve(final URI absoluteURI, final String encoding, final Configuration config) throws XPathException {
|
||||
if (isUnderBaseUri(absoluteURI)) {
|
||||
try {
|
||||
return new InputStreamReader(absoluteURI.toURL().openStream(), encoding);
|
||||
} catch (final IOException e) {
|
||||
throw new IllegalStateException(String.format("Can not resolve required %s", absoluteURI), e);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException(String.format(
|
||||
"The resolved transformation artifact %s is not within the configured repository %s", absoluteURI, this.baseUri));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package de.kosit.validationtool.impl.xml;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import javax.xml.transform.URIResolver;
|
||||
import javax.xml.validation.Schema;
|
||||
import javax.xml.validation.SchemaFactory;
|
||||
import javax.xml.validation.Validator;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import net.sf.saxon.s9api.Processor;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Andreas Penski
|
||||
*/
|
||||
@Slf4j
|
||||
public class StrictLocalResolvingStrategy extends StrictRelativeResolvingStrategy {
|
||||
|
||||
/**
|
||||
* e.g. don't allow any scheme
|
||||
*/
|
||||
|
||||
@Override
|
||||
public SchemaFactory createSchemaFactory() {
|
||||
final SchemaFactory schemaFactory = super.createSchemaFactory();
|
||||
allowExternalSchema(schemaFactory, "file", "jar");
|
||||
return schemaFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Processor createProcessor() {
|
||||
return super.createProcessor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URIResolver createResolver(final URI repository) {
|
||||
// intentionally return 'null', since all resolving is configured with the other objects
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Validator createValidator(final Schema schema) {
|
||||
final Validator validator = super.createValidator(schema);
|
||||
allowExternalSchema(validator, "file", "jar");
|
||||
return validator;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
package de.kosit.validationtool.impl.xml;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.transform.Result;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.URIResolver;
|
||||
import javax.xml.validation.Schema;
|
||||
import javax.xml.validation.SchemaFactory;
|
||||
import javax.xml.validation.Validator;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import net.sf.saxon.Configuration;
|
||||
import net.sf.saxon.expr.XPathContext;
|
||||
import net.sf.saxon.lib.CollectionFinder;
|
||||
import net.sf.saxon.lib.Feature;
|
||||
import net.sf.saxon.lib.FeatureKeys;
|
||||
import net.sf.saxon.lib.OutputURIResolver;
|
||||
import net.sf.saxon.lib.ResourceCollection;
|
||||
import net.sf.saxon.lib.UnparsedTextURIResolver;
|
||||
import net.sf.saxon.s9api.Processor;
|
||||
import net.sf.saxon.trans.XPathException;
|
||||
|
||||
/**
|
||||
* @author Andreas Penski
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class StrictRelativeResolvingStrategy extends BaseResolvingStrategy {
|
||||
|
||||
private static class SecureUriResolver implements CollectionFinder, OutputURIResolver, UnparsedTextURIResolver {
|
||||
|
||||
public static final String MESSAGE = "Configuration error. Resolving ist not allowed";
|
||||
|
||||
@Override
|
||||
public OutputURIResolver newInstance() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result resolve(final String href, final String base) throws TransformerException {
|
||||
throw new IllegalStateException(MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(final Result result) throws TransformerException {
|
||||
throw new IllegalStateException(MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader resolve(final URI absoluteURI, final String encoding, final Configuration config) throws XPathException {
|
||||
throw new IllegalStateException(MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceCollection findCollection(final XPathContext context, final String collectionURI) throws XPathException {
|
||||
throw new IllegalStateException(MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* e.g. don't allow any scheme
|
||||
*/
|
||||
private static final String EMPTY_SCHEME = "";
|
||||
|
||||
@Override
|
||||
public SchemaFactory createSchemaFactory() {
|
||||
final SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
|
||||
disableExternalEntities(sf);
|
||||
allowExternalSchema(sf, "file");
|
||||
return sf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Processor createProcessor() {
|
||||
final Processor processor = new Processor(false);
|
||||
// verhindere global im Prinzip alle resolving strategien
|
||||
final SecureUriResolver resolver = new SecureUriResolver();
|
||||
processor.getUnderlyingConfiguration().setCollectionFinder(resolver);
|
||||
processor.getUnderlyingConfiguration().setOutputURIResolver(resolver);
|
||||
processor.getUnderlyingConfiguration().setUnparsedTextURIResolver(resolver);
|
||||
|
||||
// grundsätzlich Feature-konfiguration:
|
||||
processor.setConfigurationProperty(Feature.DTD_VALIDATION, false);
|
||||
processor.setConfigurationProperty(Feature.ENTITY_RESOLVER_CLASS, "");
|
||||
processor.setConfigurationProperty(Feature.XINCLUDE, false);
|
||||
processor.setConfigurationProperty(Feature.ALLOW_EXTERNAL_FUNCTIONS, false);
|
||||
|
||||
// Konfiguration des zu verwendenden Parsers, wenn Saxon selbst einen erzeugen muss, bspw. beim XSL parsen
|
||||
processor.setConfigurationProperty(FeatureKeys.XML_PARSER_FEATURE + encode(FEATURE_SECURE_PROCESSING), true);
|
||||
processor.setConfigurationProperty(FeatureKeys.XML_PARSER_FEATURE + encode(DISSALLOW_DOCTYPE_DECL_FEATURE), true);
|
||||
processor.setConfigurationProperty(FeatureKeys.XML_PARSER_FEATURE + encode(LOAD_EXTERNAL_DTD_FEATURE), false);
|
||||
return processor;
|
||||
}
|
||||
|
||||
private static String encode(final String input) {
|
||||
try {
|
||||
return URLEncoder.encode(input, StandardCharsets.UTF_8.name());
|
||||
} catch (final UnsupportedEncodingException e) {
|
||||
throw new IllegalStateException("Error encoding property while initializing saxon", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public URIResolver createResolver(final URI repositoryURI) {
|
||||
return new RelativeUriResolver(repositoryURI);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Validator createValidator(final Schema schema) {
|
||||
if (schema == null) {
|
||||
throw new IllegalArgumentException("No schema supplied. Can not create validator");
|
||||
}
|
||||
forceOpenJdkXmlImplementation();
|
||||
final Validator validator = schema.newValidator();
|
||||
disableExternalEntities(validator);
|
||||
allowExternalSchema(validator, "file" /* allow nothing external */);
|
||||
return validator;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue