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