001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2015-2016 ForgeRock AS.
015 */
016
017package org.forgerock.util.xml;
018
019import java.lang.reflect.Method;
020import javax.xml.XMLConstants;
021import javax.xml.parsers.DocumentBuilder;
022import javax.xml.parsers.DocumentBuilderFactory;
023import javax.xml.parsers.ParserConfigurationException;
024import javax.xml.parsers.SAXParser;
025import javax.xml.parsers.SAXParserFactory;
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028import org.xml.sax.SAXException;
029
030/**
031 * Utility classes for handling XML.
032 */
033public final class XMLUtils {
034
035    private static final Logger LOGGER = LoggerFactory.getLogger(XMLUtils.class);
036    private static final Object SECURITY_MANAGER;
037    private static final Integer ENTITY_EXP_LIMIT =
038            Integer.getInteger("org.forgerock.util.xml.entity.expansion.limit", 5000);
039
040    /**
041     * When Xerces is used for XML parsing, the only way to control entityExpansionLimit is to override the default
042     * SecurityManager. The following block will ensure that a Xerces SecurityManager is created and configured to have
043     * a less permissive entityExpansionLimit.
044     * In case Xerces is not used, but the JDK's XML parser implementation is leveraged, applications should enforce
045     * entity expansion limits by following the <a href="JAXP.java.net/1.4/JAXP-Compatibility.html#JAXP_security">
046     * JAXP configuration guide</a>.
047     */
048    static {
049        Object securityManager = null;
050        try {
051            Class<?> securityManagerClass = Class.forName("org.apache.xerces.util.SecurityManager");
052            securityManager = securityManagerClass.newInstance();
053            Method setEntityExpansionLimit = securityManagerClass.getMethod("setEntityExpansionLimit", int.class);
054            setEntityExpansionLimit.invoke(securityManager, ENTITY_EXP_LIMIT);
055        } catch (ClassNotFoundException ex) {
056            LOGGER.debug("Not using Xerces");
057        } catch (Exception ex) {
058            LOGGER.debug("Unable to set expansion limit for Xerces, using default settings", ex);
059            securityManager = null;
060        }
061        SECURITY_MANAGER = securityManager;
062    }
063
064    private XMLUtils() {
065        // No impl.
066    }
067
068    /**
069     * Provides a secure DocumentBuilder implementation, which is protected against
070     * different types of entity expansion attacks and makes sure that only locally
071     * available DTDs can be referenced within the XML document.
072     * @param validating Whether the returned DocumentBuilder should validate input.
073     * @return A secure DocumentBuilder instance.
074     * @throws javax.xml.parsers.ParserConfigurationException In case xerces does not support one
075     * of the required features.
076     */
077    public static DocumentBuilder getSafeDocumentBuilder(boolean validating) throws ParserConfigurationException {
078        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
079        dbf.setValidating(validating);
080        dbf.setNamespaceAware(true);
081        dbf.setXIncludeAware(false);
082        dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
083        dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
084        dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
085        dbf.setExpandEntityReferences(false);
086        if (SECURITY_MANAGER != null) {
087            dbf.setAttribute("http://apache.org/xml/properties/security-manager", SECURITY_MANAGER);
088        }
089        try {
090            dbf.setAttribute("http://www.oracle.com/xml/jaxp/properties/entityExpansionLimit", ENTITY_EXP_LIMIT);
091        } catch (IllegalArgumentException ie) { }
092        DocumentBuilder db = dbf.newDocumentBuilder();
093        db.setEntityResolver(new XMLHandler());
094        return db;
095    }
096
097    /**
098     * Provides a secure SAXParser instance, which is protected against different
099     * types of entity expension, DoS attacks and makes sure that only locally
100     * available DTDs can be referenced within the XML document.
101     * @param validating Whether the returned DocumentBuilder should validate input.
102     * @return A secure SAXParser instance.
103     * @throws ParserConfigurationException In case Xerces does not support one of
104     * the required features.
105     * @throws SAXException In case Xerces does not support one of the required
106     * features.
107     */
108    public static SAXParser getSafeSAXParser(boolean validating) throws ParserConfigurationException, SAXException {
109        SAXParserFactory saxFactory = SAXParserFactory.newInstance();
110        saxFactory.setValidating(validating);
111        saxFactory.setNamespaceAware(true);
112        saxFactory.setXIncludeAware(false);
113        saxFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
114        saxFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
115        saxFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
116        SAXParser sp = saxFactory.newSAXParser();
117        if (SECURITY_MANAGER != null) {
118            sp.setProperty("http://apache.org/xml/properties/security-manager", SECURITY_MANAGER);
119        }
120        try {
121            sp.setProperty("http://www.oracle.com/xml/jaxp/properties/entityExpansionLimit", ENTITY_EXP_LIMIT);
122        } catch (Exception ex) { }
123        sp.getXMLReader().setEntityResolver(new XMLHandler());
124        return sp;
125    }
126}