diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a0390227..b5b71ad1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Prowide Core - CHANGELOG +#### 9.3.18 - SNAPSHOT + * Added support for an optional `pw-swift-core.properties` to customize the behavior of the SafeXmlUtils class + #### 9.3.17 - July 2023 * (PW-1405) Trim original String payload when creating an AbstractSwiftMessage diff --git a/src/main/java/com/prowidesoftware/swift/utils/PropertyLoader.java b/src/main/java/com/prowidesoftware/swift/utils/PropertyLoader.java new file mode 100644 index 000000000..f05626479 --- /dev/null +++ b/src/main/java/com/prowidesoftware/swift/utils/PropertyLoader.java @@ -0,0 +1,70 @@ +/* + * Licensed 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 com.prowidesoftware.swift.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +/** + * Helper class to load properties from a file. + * + * @since 9.3.18 + */ +class PropertyLoader { + private static final java.util.logging.Logger log = + java.util.logging.Logger.getLogger(PropertyLoader.class.getName()); + static final String PROPERTIES_FILE = "pw-swift-core.properties"; + private static Properties properties = null; + + private PropertyLoader() { + // prevent instantiation + } + + static Properties loadProperties() { + if (properties == null) { + properties = new Properties(); + try (InputStream inputStream = PropertyLoader.class.getClassLoader().getResourceAsStream(PROPERTIES_FILE)) { + if (inputStream != null) { + properties.load(inputStream); + } + } catch (IOException e) { + log.log(java.util.logging.Level.WARNING, "Error loading properties from " + PROPERTIES_FILE, e); + } + } + return properties; + } + + static String[] getPropertyArray(String key) { + Properties loadedProperties = loadProperties(); + String propertyValue = loadedProperties.getProperty(key); + + if (propertyValue != null) { + return propertyValue.split(","); + } + + return new String[0]; + } + + static String getProperty(String key) { + Properties loadedProperties = loadProperties(); + String propertyValue = loadedProperties.getProperty(key); + + if (propertyValue != null) { + return propertyValue; + } + + return null; + } +} diff --git a/src/main/java/com/prowidesoftware/swift/utils/SafeXmlUtils.java b/src/main/java/com/prowidesoftware/swift/utils/SafeXmlUtils.java index 97e445bc1..8d5a2efd8 100644 --- a/src/main/java/com/prowidesoftware/swift/utils/SafeXmlUtils.java +++ b/src/main/java/com/prowidesoftware/swift/utils/SafeXmlUtils.java @@ -16,6 +16,7 @@ package com.prowidesoftware.swift.utils; import com.prowidesoftware.ProwideException; +import java.util.logging.Level; import javax.xml.XMLConstants; import javax.xml.parsers.*; import javax.xml.stream.XMLInputFactory; @@ -25,23 +26,33 @@ import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; +import org.apache.commons.lang3.ArrayUtils; import org.xml.sax.SAXException; import org.xml.sax.SAXNotRecognizedException; import org.xml.sax.SAXNotSupportedException; import org.xml.sax.XMLReader; /** - * Reusable safe XML document builder to prevent XXE - * https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + * This class is uses in many places in the library to create XML parsers and transformers, to switch off certain + * features that may be vulnerable to XXE attacks. + *

+ * The features are implementation dependent, thus they might not be present in certain implementations of the XML apis. + * We have experience issues with many xerces and xalan versions. So when faced with an error because a feature is + * not present in your environment, the first choice should be to review the xml related dependencies, and to try to + * those that do not support the feature. + *

+ * When the dependencies cannot be changed, you can ignore the error by adding a pw-swift-core.properties file in the + * application classpath with a safeXmlUtils.ignore=featureName,featureName,featureName property. This will prevent the + * indicated features to be applied. * * @since 8.0.5 */ public class SafeXmlUtils { - @SuppressWarnings("unused") - private static final transient java.util.logging.Logger log = + private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(SafeXmlUtils.class.getName()); - // Suppress default constructor for noninstantiability + private static final String FEATURE_IGNORE_PROPERTY = "safeXmlUtils.ignore"; + private SafeXmlUtils() { throw new AssertionError(); } @@ -68,18 +79,24 @@ public static DocumentBuilder documentBuilder(boolean namespaceAware) { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); feature = XMLConstants.FEATURE_SECURE_PROCESSING; - dbf.setFeature(feature, true); + if (applyFeature(feature)) { + dbf.setFeature(feature, true); + } // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities // Using the SAXParserFactory's setFeature feature = "http://xml.org/sax/features/external-general-entities"; - dbf.setFeature(feature, false); + if (applyFeature(feature)) { + dbf.setFeature(feature, false); + } // Xerces 2 only - http://xerces.apache.org/xerces-j/features.html#external-general-entities feature = "http://apache.org/xml/features/disallow-doctype-decl"; - dbf.setFeature(feature, true); + if (applyFeature(feature)) { + dbf.setFeature(feature, true); + } // set parameter dbf.setNamespaceAware(namespaceAware); @@ -87,10 +104,7 @@ public static DocumentBuilder documentBuilder(boolean namespaceAware) { return dbf.newDocumentBuilder(); } catch (ParserConfigurationException e) { - throw new ProwideException( - "Error configuring the XML document builder. " + "The feature " + feature - + " is probably not supported by your XML processor.", - e); + throw logAndCreateException(e, feature, DocumentBuilderFactory.class.getName()); } } @@ -117,18 +131,24 @@ public static XMLReader reader(boolean namespaceAware, Schema schema) throws Pro SAXParserFactory spf = SAXParserFactory.newInstance(); feature = XMLConstants.FEATURE_SECURE_PROCESSING; - spf.setFeature(feature, true); + if (applyFeature(feature)) { + spf.setFeature(feature, true); + } // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities // Using the SAXParserFactory's setFeature feature = "http://xml.org/sax/features/external-general-entities"; - spf.setFeature(feature, false); + if (applyFeature(feature)) { + spf.setFeature(feature, false); + } // Xerces 2 only - http://xerces.apache.org/xerces-j/features.html#external-general-entities feature = "http://apache.org/xml/features/disallow-doctype-decl"; - spf.setFeature(feature, true); + if (applyFeature(feature)) { + spf.setFeature(feature, true); + } // set parameters spf.setNamespaceAware(namespaceAware); @@ -142,25 +162,30 @@ public static XMLReader reader(boolean namespaceAware, Schema schema) throws Pro // Using the XMLReader's setFeature feature = "http://apache.org/xml/features/disallow-doctype-decl"; - reader.setFeature(feature, true); + if (applyFeature(feature)) { + spf.setFeature(feature, true); + } // This may not be strictly required as DTDs shouldn't be allowed at all, per previous line. feature = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; - reader.setFeature(feature, false); + if (applyFeature(feature)) { + spf.setFeature(feature, false); + } feature = "http://xml.org/sax/features/external-general-entities"; - reader.setFeature(feature, false); + if (applyFeature(feature)) { + spf.setFeature(feature, false); + } feature = "http://xml.org/sax/features/external-parameter-entities"; - reader.setFeature(feature, false); + if (applyFeature(feature)) { + spf.setFeature(feature, false); + } return reader; } catch (ParserConfigurationException | SAXException e) { - throw new ProwideException( - "Error configuring the XML parser. " + "The feature " + feature - + " is probably not supported by your XML processor.", - e); + throw logAndCreateException(e, feature, SAXParserFactory.class.getName()); } } @@ -173,10 +198,14 @@ public static XMLInputFactory inputFactory() { XMLInputFactory xif = XMLInputFactory.newInstance(); // This disables DTDs entirely for that factory - xif.setProperty(XMLInputFactory.SUPPORT_DTD, false); - + if (applyFeature(XMLInputFactory.SUPPORT_DTD)) { + xif.setProperty(XMLInputFactory.SUPPORT_DTD, false); + } // disable external entities - xif.setProperty("javax.xml.stream.isSupportingExternalEntities", false); + String property = "javax.xml.stream.isSupportingExternalEntities"; + if (applyFeature(property)) { + xif.setProperty(property, false); + } return xif; } @@ -190,18 +219,19 @@ public static Transformer transformer() { TransformerFactory tf = TransformerFactory.newInstance(); feature = XMLConstants.ACCESS_EXTERNAL_DTD; - tf.setAttribute(feature, ""); + if (applyFeature(feature)) { + tf.setAttribute(feature, ""); + } feature = XMLConstants.ACCESS_EXTERNAL_STYLESHEET; - tf.setAttribute(feature, ""); + if (applyFeature(feature)) { + tf.setAttribute(feature, ""); + } return tf.newTransformer(); } catch (TransformerConfigurationException e) { - throw new ProwideException( - "Error configuring the XML transformer factory. " + "The feature " + feature - + " is probably not supported by your XML processor.", - e); + throw logAndCreateException(e, feature, Transformer.class.getName()); } } @@ -215,7 +245,9 @@ public static SchemaFactory schemaFactory() { // https://stackoverflow.com/questions/58374278/org-xml-sax-saxnotrecognizedexception-property-http-javax-xml-xmlconstants-p feature = XMLConstants.ACCESS_EXTERNAL_DTD; - factory.setProperty(feature, ""); + if (applyFeature(feature)) { + factory.setProperty(feature, ""); + } // we keep this one for the moment because it is needed in MX xsys validation // feature = XMLConstants.ACCESS_EXTERNAL_SCHEMA; @@ -224,10 +256,7 @@ public static SchemaFactory schemaFactory() { return factory; } catch (SAXNotRecognizedException | SAXNotSupportedException e) { - throw new ProwideException( - "Error configuring the schema factory. " + "The feature " + feature - + " is probably not supported by your XML processor.", - e); + throw logAndCreateException(e, feature, SchemaFactory.class.getName()); } } @@ -240,18 +269,41 @@ public static Validator validator(Schema schema) { Validator validator = schema.newValidator(); feature = XMLConstants.ACCESS_EXTERNAL_DTD; - validator.setProperty(feature, ""); + if (applyFeature(feature)) { + validator.setProperty(feature, ""); + } feature = XMLConstants.ACCESS_EXTERNAL_SCHEMA; - validator.setProperty(feature, ""); + if (applyFeature(feature)) { + validator.setProperty(feature, ""); + } return validator; } catch (SAXNotRecognizedException | SAXNotSupportedException e) { - throw new ProwideException( - "Error configuring the schema validator. " + "The feature " + feature - + " is probably not supported by your XML processor.", - e); + throw logAndCreateException(e, feature, Validator.class.getName()); } } + + private static boolean applyFeature(final String feature) { + final String[] prop = PropertyLoader.getPropertyArray(FEATURE_IGNORE_PROPERTY); + return (!ArrayUtils.contains(prop, feature)); + } + + private static ProwideException logAndCreateException(Exception e, String feature, String className) { + log.log( + Level.SEVERE, + "Error configuring the " + className + ". The feature " + feature + + " is not supported by your XML processor. Increase log level for details."); + log.log( + Level.FINER, + "To avoid the missing feature, try removing the xerces and xalan dependencies in your project. If that is not doable, you can ignore this error by adding a " + + PropertyLoader.PROPERTIES_FILE + " in your application classpath with property " + + FEATURE_IGNORE_PROPERTY + "=" + feature, + e); + return new ProwideException( + "Error configuring the " + className + ". The feature " + feature + + " is not supported by your XML processor.", + e); + } }