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