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 /** 177 * Class name of Android's Harmony-based {@link DocumentBuilderFactory}, which exposes no secure surface. 178 */ 179 private static final String ANDROID_DOCUMENT_BUILDER_FACTORY = "org.apache.harmony.xml.parsers.DocumentBuilderFactoryImpl"; 180 /** 181 * System property naming the {@link DocumentBuilderFactory} implementation, the JDK's own mechanism for reconfiguring the default parser. 182 */ 183 private static final String DOM_FACTORY_ID = "javax.xml.parsers.DocumentBuilderFactory"; 184 185 /** 186 * Class name of the JDK's built-in default implementation, the Java 8 fallback for {@link #newDefaultInstance()}. 187 */ 188 static final String JDK_DOCUMENT_BUILDER_FACTORY = "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl"; 189 190 private static final MethodHandle MH_newDefaultInstance = MethodHandleFactory.findStatic(DocumentBuilderFactory.class, "newDefaultInstance"); 191 192 /** 193 * Enables namespace awareness on the given factory; the {@code NSInstance} counterpart of each factory method routes its result through here. 194 * 195 * @param factory The factory to configure; never {@code null}. 196 * @return The given factory, namespace-aware. 197 */ 198 private static DocumentBuilderFactory makeNSAware(final DocumentBuilderFactory factory) { 199 factory.setNamespaceAware(true); 200 return factory; 201 } 202 203 /** 204 * Returns a new, secure {@link DocumentBuilderFactory} of the system-default implementation. 205 * <p> 206 * Obtained from {@code DocumentBuilderFactory.newDefaultInstance()} where the platform provides it (Java 9 or later), 207 * by instantiating the JDK's built-in implementation directly on Java 8, 208 * and by the standard {@link #newInstance()} lookup where the platform provides neither 209 * (for example, Android, whose lookup is itself pinned to the platform implementation). 210 * </p> 211 * 212 * @return A secure factory. 213 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation. 214 * @throws FactoryConfigurationError Thrown from the {@link #newInstance()} lookup this method falls back to on a platform that provides neither 215 * {@code newDefaultInstance()} nor the JDK's built-in implementation (for example, Android). 216 */ 217 public static DocumentBuilderFactory newDefaultInstance() { 218 if (MH_newDefaultInstance != null) { 219 return secure(MethodHandleFactory.invokeExact(() -> (DocumentBuilderFactory) MH_newDefaultInstance.invokeExact(), FactoryConfigurationError.class)); 220 } 221 try { 222 // Java 8: the method does not exist; instantiate the JDK's built-in default by its class name instead. 223 return newInstance(JDK_DOCUMENT_BUILDER_FACTORY, null); 224 } catch (final FactoryConfigurationError e) { 225 // Neither exists (for example, Android): degrade to the regular lookup, which such platforms pin to their built-in parser. 226 return newInstance(); 227 } 228 } 229 230 /** 231 * Returns a new, secure, namespace-aware {@link DocumentBuilderFactory} of the system-default implementation, enabling namespace awareness on 232 * {@link #newDefaultInstance()}, the behavior {@code DocumentBuilderFactory.newDefaultNSInstance()} (Java 13 or later) is specified to have. 233 * 234 * @return A secure, namespace-aware factory. 235 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation. 236 * @throws FactoryConfigurationError Thrown from the {@link #newInstance()} lookup {@link #newDefaultInstance()} falls back to on a platform that provides 237 * neither {@code newDefaultInstance()} nor the JDK's built-in implementation (for example, Android). 238 */ 239 public static DocumentBuilderFactory newDefaultNSInstance() { 240 return makeNSAware(newDefaultInstance()); 241 } 242 243 /** 244 * Returns a new, secure {@link DocumentBuilderFactory}. 245 * 246 * @return A secure factory. 247 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation. 248 * @throws IllegalStateException Thrown if a (non-Android) factory cannot support the secure processing feature 249 * {@link XMLConstants#FEATURE_SECURE_PROCESSING}. 250 * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service configuration error} or if the 251 * implementation is not available or cannot be instantiated. 252 */ 253 public static DocumentBuilderFactory newInstance() { 254 return secure(DocumentBuilderFactory.newInstance()); 255 } 256 257 /** 258 * Returns a new, secure {@link DocumentBuilderFactory} of the given implementation class. 259 * 260 * @param factoryClassName The fully qualified class name of the {@link DocumentBuilderFactory} implementation. 261 * @param classLoader The class loader used to load the factory class; {@code null} means the current thread's context class loader. 262 * @return A secure factory. 263 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation. 264 * @throws IllegalStateException Thrown if a (non-Android) factory cannot support the secure processing feature 265 * {@link XMLConstants#FEATURE_SECURE_PROCESSING}. 266 * @throws FactoryConfigurationError Thrown if {@code factoryClassName} is {@code null} or the factory class cannot be loaded or instantiated. 267 */ 268 public static DocumentBuilderFactory newInstance(final String factoryClassName, final ClassLoader classLoader) { 269 return secure(DocumentBuilderFactory.newInstance(factoryClassName, classLoader)); 270 } 271 272 /** 273 * Creates a new, secure, namespace-aware {@link DocumentBuilder} from {@link #newNSInstance()}. 274 * <p> 275 * No factory is cached: each call configures a fresh one. To parse many documents, keep the returned builder and call {@link DocumentBuilder#reset()} 276 * between documents. Reusing the builder saves more than caching the factory would, and {@code reset()} costs next to nothing while keeping handler state 277 * from leaking between parses. A builder is not thread-safe, so reuse it within one thread. 278 * </p> 279 * 280 * @return A secure, namespace-aware builder. 281 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation, or if the implementation cannot 282 * create a builder. 283 * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service configuration error} or if the 284 * implementation is not available or cannot be instantiated. 285 * @since 1.1.0 286 */ 287 public static DocumentBuilder newNSDocumentBuilder() { 288 try { 289 return newNSInstance().newDocumentBuilder(); 290 } catch (final ParserConfigurationException e) { 291 // Implementations reject settings when they are set on the factory, not here: a failure means a broken environment. 292 throw SecureException.creationFailed(DocumentBuilder.class, e); 293 } 294 } 295 296 /** 297 * Returns a new, secure, namespace-aware {@link DocumentBuilderFactory}, enabling namespace awareness on {@link #newInstance()}, the behavior 298 * {@code DocumentBuilderFactory.newNSInstance()} (Java 13 or later) is specified to have. 299 * 300 * @return A secure, namespace-aware factory. 301 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation. 302 * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service configuration error} or if the 303 * implementation is not available or cannot be instantiated. 304 */ 305 public static DocumentBuilderFactory newNSInstance() { 306 return makeNSAware(newInstance()); 307 } 308 309 /** 310 * Returns the secure, namespace-aware factory the Source-rewriting wrappers parse with. 311 * <p> 312 * While {@code overrideDefaultParser} is {@code false}, the factory is the JDK's "default parser" factory, determined the way the JDK itself determines it: 313 * the built-in implementation, unless the {@value #DOM_FACTORY_ID} system property is set. That property is the JDK's own mechanism for 314 * reconfiguring the default parser, so it is honored through the standard lookup rather than bypassed. 315 * </p> 316 * 317 * @param overrideDefaultParser whether {@value SecureSAXParserFactory#OVERRIDE_DEFAULT_PARSER} on the originating factory asks to override the JDK's 318 * default parser. 319 * @return A secure, namespace-aware factory. 320 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation. 321 * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service configuration error} or if the 322 * implementation is not available or cannot be instantiated. 323 */ 324 static DocumentBuilderFactory newNSInstance(final boolean overrideDefaultParser) { 325 return overrideDefaultParser || System.getProperty(DOM_FACTORY_ID) != null ? newNSInstance() : newDefaultNSInstance(); 326 } 327 328 /** 329 * Returns a new, secure, namespace-aware {@link DocumentBuilderFactory} of the given implementation class, enabling namespace awareness on 330 * {@link #newInstance(String, ClassLoader)}, the behavior {@code DocumentBuilderFactory.newNSInstance(String, ClassLoader)} (Java 13 or later) is specified 331 * to have. 332 * 333 * @param factoryClassName The fully qualified class name of the {@link DocumentBuilderFactory} implementation. 334 * @param classLoader The class loader used to load the factory class; {@code null} means the current thread's context class loader. 335 * @return A secure, namespace-aware factory. 336 * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation. 337 * @throws FactoryConfigurationError Thrown if {@code factoryClassName} is {@code null} or the factory class cannot be loaded or instantiated. 338 */ 339 public static DocumentBuilderFactory newNSInstance(final String factoryClassName, final ClassLoader classLoader) { 340 return makeNSAware(newInstance(factoryClassName, classLoader)); 341 } 342 343 /** 344 * Applies capability-driven secure settings to any {@link DocumentBuilderFactory} on the classpath. 345 * 346 * <p> 347 * Rather than branching on the implementation class, this method probes what the factory supports and adapts: 348 * </p> 349 * <ul> 350 * <li><strong>Android</strong> (Harmony / KXmlParser): recognized by class name and left untouched. It exposes no {@link 351 * XMLConstants#FEATURE_SECURE_PROCESSING 352 * 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 353 * apply.</li> 354 * <li><strong>FSP</strong>: required. It switches on the implementation's built-in security manager, which is what carries the processing limits.</li> 355 * <li><strong>Ignore-all resolver floor</strong>: every produced {@link DocumentBuilder} is wrapped by the nested wrapper, which keeps an 356 * ignore-all {@link EntityResolver} floor. That floor blocks external DTD, entity, schema and {@code xi:include} fetches in one place: the stock JDK's 357 * XInclude processor ignores {@code ACCESS_EXTERNAL_*} and consults the {@link EntityResolver} instead, so no {@code ACCESS_EXTERNAL_*} attributes are 358 * needed here. A caller can chain its own resolver onto the floor to allow-list resources, but cannot remove it.</li> 359 * </ul> 360 * 361 * @param factory The factory to secure. 362 * @return A new secure factory or the original factory, as-is, if it is a known Android factory. 363 * @throws SecureException Thrown if a (non-Android) factory cannot support the secure processing feature {@link XMLConstants#FEATURE_SECURE_PROCESSING}. 364 */ 365 static DocumentBuilderFactory secure(final DocumentBuilderFactory factory) { 366 // Android exposes no FSP, ACCESS_EXTERNAL_* or attribute API, and KXmlParser drops user-defined entities; nothing to apply. 367 if (ANDROID_DOCUMENT_BUILDER_FACTORY.equals(factory.getClass().getName())) { 368 return factory; 369 } 370 // Required: enables the implementation's security manager, which carries the limits. 371 setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true); 372 // Required: the wrapper installs an ignore-all EntityResolver floor on every DocumentBuilder. 373 // That floor blocks external DTD, entity, schema and xi:include fetches in one place: no ACCESS_EXTERNAL_* attributes are needed here. 374 // Callers can chain their resolvers, but not override the floor. 375 return new Wrapper(factory); 376 } 377 378 /** 379 * Sets a feature on the given factory, throwing a {@link SecureException} if the implementation does not recognize it. 380 * 381 * @param factory The factory to secure. 382 * @param feature The feature to set. 383 * @param value The value to set. 384 * @throws SecureException Thrown if this factory cannot support this feature. 385 * @throws NullPointerException Thrown if the {@code feature} parameter is null. 386 */ 387 private static void setFeature(final DocumentBuilderFactory factory, final String feature, final boolean value) { 388 try { 389 factory.setFeature(feature, value); 390 } catch (final ParserConfigurationException e) { 391 throw SecureException.featureFailed(feature, factory, e); 392 } 393 } 394 395 private SecureDocumentBuilderFactory() { 396 // static only 397 } 398}