001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.xml.secure; 019 020import java.lang.invoke.MethodHandle; 021import java.util.Objects; 022 023import javax.xml.XMLConstants; 024import javax.xml.parsers.DocumentBuilder; 025import javax.xml.parsers.DocumentBuilderFactory; 026import javax.xml.parsers.FactoryConfigurationError; 027import javax.xml.parsers.ParserConfigurationException; 028import javax.xml.validation.Schema; 029 030import org.xml.sax.EntityResolver; 031 032/** 033 * Creates new, secure {@link DocumentBuilderFactory} instances. 034 * <p> 035 * Beyond the three universal guarantees on {@link org.apache.commons.xml.secure}, XInclude resolution is denied by default. When 036 * {@link DocumentBuilderFactory#setXIncludeAware(boolean) setXIncludeAware(true)} is called on the returned factory, the parser will process {@code xi:include} 037 * elements but every external resource lookup is rejected. To permit specific trusted resources, install an {@link org.xml.sax.EntityResolver EntityResolver} 038 * on the {@link DocumentBuilder} that allow-lists them; any href the resolver does not explicitly allow stays blocked. 039 * </p> 040 * <p> 041 * This class is not itself a {@link DocumentBuilderFactory}, so it inherits none of the static JAXP factory methods. A caller therefore cannot obtain an 042 * unsecured factory through this class by calling a method such as {@code newDefaultInstance()}. The secure factories are instances of a nested, non-public 043 * wrapper class. 044 * </p> 045 * 046 * @see org.apache.commons.xml.secure 047 */ 048public final class SecureDocumentBuilderFactory { 049 050 /** 051 * {@link DocumentBuilderFactory} wrapper that keeps an ignore-all {@link EntityResolver} floor on every {@link DocumentBuilder} produced. 052 * <p> 053 * Wraps each produced builder in a {@link SecureDocumentBuilder}; required when the underlying factory carries no resolver of its own and does not honor 054 * JAXP 1.5 {@code ACCESS_EXTERNAL_*} (e.g. the external Xerces distribution). A caller-set resolver is routed through the floor rather than replacing it. 055 * </p> 056 */ 057 private static final class Wrapper extends DocumentBuilderFactory { 058 059 private final DocumentBuilderFactory delegate; 060 061 /** 062 * Constructs a new instance. 063 * 064 * @param delegate The delegate to wrap; must not be {@code null}. 065 * @throws NullPointerException Thrown if {@code delegate} is {@code null}. 066 */ 067 private Wrapper(final DocumentBuilderFactory delegate) { 068 this.delegate = Objects.requireNonNull(delegate, "delegate"); 069 } 070 071 @Override 072 public Object getAttribute(final String name) { 073 return delegate.getAttribute(name); 074 } 075 076 @Override 077 public boolean getFeature(final String name) throws ParserConfigurationException { 078 return delegate.getFeature(name); 079 } 080 081 @Override 082 public Schema getSchema() { 083 return delegate.getSchema(); 084 } 085 086 @Override 087 public boolean isCoalescing() { 088 return delegate.isCoalescing(); 089 } 090 091 @Override 092 public boolean isExpandEntityReferences() { 093 return delegate.isExpandEntityReferences(); 094 } 095 096 @Override 097 public boolean isIgnoringComments() { 098 return delegate.isIgnoringComments(); 099 } 100 101 @Override 102 public boolean isIgnoringElementContentWhitespace() { 103 return delegate.isIgnoringElementContentWhitespace(); 104 } 105 106 @Override 107 public boolean isNamespaceAware() { 108 return delegate.isNamespaceAware(); 109 } 110 111 @Override 112 public boolean isValidating() { 113 return delegate.isValidating(); 114 } 115 116 @Override 117 public boolean isXIncludeAware() { 118 return delegate.isXIncludeAware(); 119 } 120 121 @Override 122 public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException { 123 return new SecureDocumentBuilder(delegate.newDocumentBuilder()); 124 } 125 126 @Override 127 public void setAttribute(final String name, final Object value) { 128 delegate.setAttribute(name, value); 129 } 130 131 @Override 132 public void setCoalescing(final boolean coalescing) { 133 delegate.setCoalescing(coalescing); 134 } 135 136 @Override 137 public void setExpandEntityReferences(final boolean expandEntityRef) { 138 delegate.setExpandEntityReferences(expandEntityRef); 139 } 140 141 @Override 142 public void setFeature(final String name, final boolean value) throws ParserConfigurationException { 143 delegate.setFeature(name, value); 144 } 145 146 @Override 147 public void setIgnoringComments(final boolean ignoreComments) { 148 delegate.setIgnoringComments(ignoreComments); 149 } 150 151 @Override 152 public void setIgnoringElementContentWhitespace(final boolean whitespace) { 153 delegate.setIgnoringElementContentWhitespace(whitespace); 154 } 155 156 @Override 157 public void setNamespaceAware(final boolean awareness) { 158 delegate.setNamespaceAware(awareness); 159 } 160 161 @Override 162 public void setSchema(final Schema schema) { 163 delegate.setSchema(schema); 164 } 165 166 @Override 167 public void setValidating(final boolean validating) { 168 delegate.setValidating(validating); 169 } 170 171 @Override 172 public void setXIncludeAware(final boolean state) { 173 delegate.setXIncludeAware(state); 174 } 175 } 176 /** Class name of Android's Harmony-based {@link DocumentBuilderFactory}, which exposes no secure surface. */ 177 private static final String ANDROID_DOCUMENT_BUILDER_FACTORY = "org.apache.harmony.xml.parsers.DocumentBuilderFactoryImpl"; 178 /** System property naming the {@link DocumentBuilderFactory} implementation, the JDK's own mechanism for reconfiguring the default parser. */ 179 private static final String DOM_FACTORY_ID = "javax.xml.parsers.DocumentBuilderFactory"; 180 181 /** Class name of the JDK's built-in default implementation, the Java 8 fallback for {@link #newDefaultInstance()}. */ 182 static final String JDK_DOCUMENT_BUILDER_FACTORY = "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl"; 183 184 private static final MethodHandle MH_newDefaultInstance = MethodHandleFactory.findStatic(DocumentBuilderFactory.class, "newDefaultInstance"); 185 186 /** 187 * Enables namespace awareness on the given factory; the {@code NSInstance} counterpart of each factory method routes its result through here. 188 * 189 * @param factory The factory to configure; never {@code null}. 190 * @return The given factory, namespace-aware. 191 */ 192 private static DocumentBuilderFactory makeNSAware(final DocumentBuilderFactory factory) { 193 factory.setNamespaceAware(true); 194 return factory; 195 } 196 197 /** 198 * Returns a new, secure {@link DocumentBuilderFactory} of the system-default implementation. 199 * <p> 200 * Obtained from {@code DocumentBuilderFactory.newDefaultInstance()} where the platform provides it (Java 9 or later), 201 * by instantiating the JDK's built-in implementation directly on Java 8, 202 * and by the standard {@link #newInstance()} lookup where the platform provides neither 203 * (for example, Android, whose lookup is itself pinned to the platform implementation). 204 * </p> 205 * 206 * @return A secure factory. 207 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation. 208 * @throws FactoryConfigurationError Thrown from the {@link #newInstance()} lookup this method falls back to on a platform that provides neither 209 * {@code newDefaultInstance()} nor the JDK's built-in implementation (for example Android). 210 */ 211 public static DocumentBuilderFactory newDefaultInstance() { 212 if (MH_newDefaultInstance != null) { 213 return secure(MethodHandleFactory.invokeExact(() -> (DocumentBuilderFactory) MH_newDefaultInstance.invokeExact(), FactoryConfigurationError.class)); 214 } 215 try { 216 // Java 8: the method does not exist; instantiate the JDK's built-in default by its class name instead. 217 return newInstance(JDK_DOCUMENT_BUILDER_FACTORY, null); 218 } catch (final FactoryConfigurationError e) { 219 // Neither exists (for example, Android): degrade to the regular lookup, which such platforms pin to their built-in parser. 220 return newInstance(); 221 } 222 } 223 224 /** 225 * Returns a new, secure, namespace-aware {@link DocumentBuilderFactory} of the system-default implementation, enabling namespace awareness on 226 * {@link #newDefaultInstance()}, the behavior {@code DocumentBuilderFactory.newDefaultNSInstance()} (Java 13 or later) is specified to have. 227 * 228 * @return A secure, namespace-aware factory. 229 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation. 230 * @throws FactoryConfigurationError Thrown from the {@link #newInstance()} lookup {@link #newDefaultInstance()} falls back to on a platform that provides 231 * neither {@code newDefaultInstance()} nor the JDK's built-in implementation (for example Android). 232 */ 233 public static DocumentBuilderFactory newDefaultNSInstance() { 234 return makeNSAware(newDefaultInstance()); 235 } 236 237 /** 238 * Returns a new, secure {@link DocumentBuilderFactory}. 239 * 240 * @return A secure factory. 241 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation. 242 * @throws IllegalStateException Thrown if a (non-Android) factory cannot support the secure processing feature 243 * {@link XMLConstants#FEATURE_SECURE_PROCESSING}. 244 * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service configuration error} or if the 245 * implementation is not available or cannot be instantiated. 246 */ 247 public static DocumentBuilderFactory newInstance() { 248 return secure(DocumentBuilderFactory.newInstance()); 249 } 250 251 /** 252 * Returns a new, secure {@link DocumentBuilderFactory} of the given implementation class. 253 * 254 * @param factoryClassName The fully qualified class name of the {@link DocumentBuilderFactory} implementation. 255 * @param classLoader The class loader used to load the factory class; {@code null} means the current thread's context class loader. 256 * @return A secure factory. 257 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation. 258 * @throws IllegalStateException Thrown if a (non-Android) factory cannot support the secure processing feature 259 * {@link XMLConstants#FEATURE_SECURE_PROCESSING}. 260 * @throws FactoryConfigurationError Thrown if {@code factoryClassName} is {@code null} or the factory class cannot be loaded or instantiated. 261 */ 262 public static DocumentBuilderFactory newInstance(final String factoryClassName, final ClassLoader classLoader) { 263 return secure(DocumentBuilderFactory.newInstance(factoryClassName, classLoader)); 264 } 265 266 /** 267 * Returns a new, secure, namespace-aware {@link DocumentBuilderFactory}, enabling namespace awareness on {@link #newInstance()}, the behavior 268 * {@code DocumentBuilderFactory.newNSInstance()} (Java 13 or later) is specified to have. 269 * 270 * @return A secure, namespace-aware factory. 271 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation. 272 * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service configuration error} or if the 273 * implementation is not available or cannot be instantiated. 274 */ 275 public static DocumentBuilderFactory newNSInstance() { 276 return makeNSAware(newInstance()); 277 } 278 279 /** 280 * Returns the secure, namespace-aware factory the Source-rewriting wrappers parse with. 281 * <p> 282 * While {@code overrideDefaultParser} is {@code false} the factory is the JDK's "default parser" factory, determined the way the JDK itself determines it: the built-in 283 * implementation, unless the {@value #DOM_FACTORY_ID} system property is set — that property is the JDK's own mechanism for 284 * reconfiguring the default parser, so it is honored through the standard lookup rather than bypassed. 285 * </p> 286 * 287 * @param overrideDefaultParser whether {@value SecureSAXParserFactory#OVERRIDE_DEFAULT_PARSER} on the originating factory asks to override the JDK's default parser. 288 * @return A secure, namespace-aware factory. 289 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation. 290 * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service configuration error} or if the 291 * implementation is not available or cannot be instantiated. 292 */ 293 static DocumentBuilderFactory newNSInstance(final boolean overrideDefaultParser) { 294 return overrideDefaultParser || System.getProperty(DOM_FACTORY_ID) != null ? newNSInstance() : newDefaultNSInstance(); 295 } 296 297 /** 298 * Returns a new, secure, namespace-aware {@link DocumentBuilderFactory} of the given implementation class, enabling namespace awareness on 299 * {@link #newInstance(String, ClassLoader)}, the behavior {@code DocumentBuilderFactory.newNSInstance(String, ClassLoader)} (Java 13 or later) is specified 300 * to have. 301 * 302 * @param factoryClassName The fully qualified class name of the {@link DocumentBuilderFactory} implementation. 303 * @param classLoader The class loader used to load the factory class; {@code null} means the current thread's context class loader. 304 * @return A secure, namespace-aware factory. 305 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation. 306 * @throws FactoryConfigurationError Thrown if {@code factoryClassName} is {@code null} or the factory class cannot be loaded or instantiated. 307 */ 308 public static DocumentBuilderFactory newNSInstance(final String factoryClassName, final ClassLoader classLoader) { 309 return makeNSAware(newInstance(factoryClassName, classLoader)); 310 } 311 312 /** 313 * Capability-driven secure for any {@link DocumentBuilderFactory} on the classpath. 314 * 315 * <p>Rather than branching on the implementation class, this method probes what the factory supports and adapts:</p> 316 * <ul> 317 * <li><strong>Android</strong> (Harmony / KXmlParser): recognized by class name and left untouched. It exposes no {@link XMLConstants#FEATURE_SECURE_PROCESSING 318 * FSP}, no JAXP 1.5 {@code ACCESS_EXTERNAL_*} and no attribute API at all, while KXmlParser silently drops user-defined entities, so there is nothing to 319 * apply.</li> 320 * <li><strong>FSP</strong>: required. It switches on the implementation's built-in security manager, which is what carries the processing limits.</li> 321 * <li><strong>Ignore-all resolver floor</strong>: every produced {@link DocumentBuilder} is wrapped by the nested wrapper, which keeps an 322 * ignore-all {@link EntityResolver} floor. That floor blocks external DTD, entity, schema and {@code xi:include} fetches in one place: the stock JDK's 323 * XInclude processor ignores {@code ACCESS_EXTERNAL_*} and consults the {@link EntityResolver} instead, so no {@code ACCESS_EXTERNAL_*} attributes are 324 * needed here. A caller can chain its own resolver onto the floor to allow-list resources, but cannot remove it.</li> 325 * </ul> 326 * 327 * @param factory The factory to secure. 328 * @return A new secure factory or the original factory, as-is, if it is a known Android factory. 329 * @throws SecureException Thrown if a (non-Android) factory cannot support the secure processing feature {@link XMLConstants#FEATURE_SECURE_PROCESSING}. 330 */ 331 static DocumentBuilderFactory secure(final DocumentBuilderFactory factory) { 332 // Android exposes no FSP, ACCESS_EXTERNAL_* or attribute API, and KXmlParser drops user-defined entities; nothing to apply. 333 if (ANDROID_DOCUMENT_BUILDER_FACTORY.equals(factory.getClass().getName())) { 334 return factory; 335 } 336 // Required: enables the implementation's security manager, which carries the limits. 337 setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true); 338 // Required: the wrapper installs an ignore-all EntityResolver floor on every DocumentBuilder. 339 // That floor blocks external DTD, entity, schema and xi:include fetches in one place: no ACCESS_EXTERNAL_* attributes are needed here. 340 // Callers can chain their resolvers, but not override the floor. 341 return new Wrapper(factory); 342 } 343 344 /** 345 * Sets a feature on the given factory, throwing a {@link SecureException} if the implementation does not recognize it. 346 * 347 * @param factory The factory to secure. 348 * @param feature The feature to set. 349 * @param value The value to set. 350 * @throws SecureException Thrown if this factory cannot support this feature. 351 * @throws NullPointerException Thrown if the {@code feature} parameter is null. 352 */ 353 private static void setFeature(final DocumentBuilderFactory factory, final String feature, final boolean value) { 354 try { 355 factory.setFeature(feature, value); 356 } catch (final ParserConfigurationException e) { 357 throw SecureException.featureFailed(feature, factory, e); 358 } 359 } 360 361 private SecureDocumentBuilderFactory() { 362 // static only 363 } 364}