org.jaggeryjs.modules.sso.common.util.Util.java Source code

Java tutorial

Introduction

Here is the source code for org.jaggeryjs.modules.sso.common.util.Util.java

Source

/*
*  Copyright (c) 2005-2010, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
*  WSO2 Inc. licenses this file to you under the Apache License,
*  Version 2.0 (the "License"); you may not use this file except
*  in compliance with the License.
*  You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.jaggeryjs.modules.sso.common.util;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xerces.impl.Constants;
import org.apache.xerces.util.SecurityManager;
import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.signature.Reference;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.utils.IdResolver;
import org.jaggeryjs.modules.sso.common.constants.SSOConstants;
import org.joda.time.DateTime;
import org.opensaml.Configuration;
import org.opensaml.DefaultBootstrap;
import org.opensaml.common.SignableSAMLObject;
import org.opensaml.common.impl.SecureRandomIdentifierGenerator;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml2.core.Assertion;
import org.opensaml.saml2.core.Audience;
import org.opensaml.saml2.core.AudienceRestriction;
import org.opensaml.saml2.core.Conditions;
import org.opensaml.saml2.core.NameID;
import org.opensaml.saml2.core.NameIDPolicy;
import org.opensaml.saml2.core.Response;
import org.opensaml.saml2.core.impl.NameIDBuilder;
import org.opensaml.saml2.core.impl.NameIDPolicyBuilder;
import org.opensaml.saml2.core.impl.StatusResponseTypeImpl;
import org.opensaml.security.SAMLSignatureProfileValidator;
import org.opensaml.xml.ConfigurationException;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.XMLObjectBuilder;
import org.opensaml.xml.io.Marshaller;
import org.opensaml.xml.io.MarshallerFactory;
import org.opensaml.xml.io.Unmarshaller;
import org.opensaml.xml.io.UnmarshallerFactory;
import org.opensaml.xml.signature.Signature;
import org.opensaml.xml.signature.SignatureValidator;
import org.opensaml.xml.signature.impl.SignatureImpl;
import org.opensaml.xml.util.Base64;
import org.opensaml.xml.util.DatatypeHelper;
import org.opensaml.xml.validation.ValidationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;
import org.wso2.carbon.base.MultitenantConstants;
import org.wso2.carbon.core.util.KeyStoreManager;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

public class Util {

    private static final int ENTITY_EXPANSION_LIMIT = 0;
    private static boolean bootStrapped = false;
    private static final String ISSUER = "issuer";
    private static final String IDENTITY_PROVIDER_ERROR = "urn:oasis:names:tc:SAML:2.0:status:Responder";
    private static final String NO_PASSIVE = "urn:oasis:names:tc:SAML:2.0:status:NoPassive";
    private static final String TIME_STAMP_SKEW = "timeStampSkew";
    private static final int DEAFAULT_TIME_STAMP_SKEW_IN_SECONDS = 300;

    private static Random random = new Random();

    private static final char[] charMapping = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
            'n', 'o', 'p' };

    private static Log log = LogFactory.getLog(Util.class);

    /**
     * This method is used to initialize the OpenSAML2 library. It calls the bootstrap method, if it
     * is not initialized yet.
     */
    public static void doBootstrap() {
        if (!bootStrapped) {
            try {
                DefaultBootstrap.bootstrap();
                bootStrapped = true;
            } catch (ConfigurationException e) {
                System.err.println("Error in bootstrapping the OpenSAML2 library");
                e.printStackTrace();
            }
        }
    }

    public static XMLObject buildXMLObject(QName objectQName) throws Exception {

        XMLObjectBuilder builder = org.opensaml.xml.Configuration.getBuilderFactory().getBuilder(objectQName);
        if (builder == null) {
            throw new Exception("Unable to retrieve builder for object QName " + objectQName);
        }
        return builder.buildObject(objectQName.getNamespaceURI(), objectQName.getLocalPart(),
                objectQName.getPrefix());
    }

    /**
     * Generates a unique Id for Authentication Requests
     *
     * @return generated unique ID
     */
    public static String createID() {

        try {
            SecureRandomIdentifierGenerator generator = new SecureRandomIdentifierGenerator();
            return generator.generateIdentifier();
        } catch (NoSuchAlgorithmException e) {
            log.warn("Error while building Secure Random ID");
        }
        return null;
    }

    /**
     * Constructing the XMLObject Object from a String
     *
     * @param authReqStr
     * @return Corresponding XMLObject which is a SAML2 object
     * @throws Exception
     */
    public static XMLObject unmarshall(String authReqStr) throws Exception {
        XMLObject response;
        try {
            doBootstrap();
            DocumentBuilderFactory documentBuilderFactory = getSecuredDocumentBuilder();
            DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder();
            Document document = docBuilder.parse(new ByteArrayInputStream(authReqStr.trim().getBytes()));
            Element element = document.getDocumentElement();
            UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();
            Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(element);
            response = unmarshaller.unmarshall(element);
            NodeList list = response.getDOM().getElementsByTagNameNS(SAMLConstants.SAML20P_NS, "Response");
            if (list.getLength() > 0) {
                log.error("Invalid schema for the SAML2 reponse. Multiple response objects found");
                throw new Exception(
                        "Error occured while processing saml2 response. Multiple response objects found");
            }
            NodeList assertionList = response.getDOM().getElementsByTagNameNS(SAMLConstants.SAML20_NS, "Assertion");
            if (response instanceof Assertion) {
                if (assertionList.getLength() > 0) {
                    log.error("Invalid schema for the SAML2 assertion. Multiple assertions detected");
                    throw new Exception(
                            "Error occurred while processing saml2 response. Multiple assertions detected");
                }
            } else {
                if (assertionList.getLength() > 1) {
                    log.error("Invalid schema for the SAML2 response. Multiple assertions detected");
                    throw new Exception(
                            "Error occurred while processing saml2 response. Multiple assertions detected");
                }
            }

            return response;
        } catch (Exception e) {
            throw new Exception("Error in constructing AuthRequest from " + "the encoded String ", e);
        }
    }

    /**
     * Serializing a SAML2 object into a String
     *
     * @param xmlObject object that needs to serialized.
     * @return serialized object
     * @throws Exception
     */
    public static String marshall(XMLObject xmlObject) throws Exception {
        try {
            doBootstrap();
            System.setProperty("javax.xml.parsers.DocumentBuilderFactory",
                    "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");

            MarshallerFactory marshallerFactory = org.opensaml.xml.Configuration.getMarshallerFactory();
            Marshaller marshaller = marshallerFactory.getMarshaller(xmlObject);
            Element element = marshaller.marshall(xmlObject);

            ByteArrayOutputStream byteArrayOutputStrm = new ByteArrayOutputStream();
            DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
            DOMImplementationLS impl = (DOMImplementationLS) registry.getDOMImplementation("LS");
            LSSerializer writer = impl.createLSSerializer();
            LSOutput output = impl.createLSOutput();
            output.setByteStream(byteArrayOutputStrm);
            writer.write(element, output);
            return byteArrayOutputStrm.toString();
        } catch (Exception e) {
            throw new Exception("Error Serializing the SAML Response", e);
        }
    }

    /**
     * Encoding the response
     *
     * @param xmlString
     *            String to be encoded
     * @return encoded String
     */
    public static String encode(String xmlString) {
        // Encoding the message
        String encodedRequestMessage = Base64.encodeBytes(xmlString.getBytes(), Base64.DONT_BREAK_LINES);
        return encodedRequestMessage.trim();
    }

    /*    *//**
            * Compressing and Encoding the response
            *
            * @param xmlString String to be encoded
            * @return compressed and encoded String
            *//*
               public static String encode(String xmlString) throws Exception {
               Deflater deflater = new Deflater(Deflater.DEFLATED, true);
               ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
               DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(
                       byteArrayOutputStream, deflater);
                   
               deflaterOutputStream.write(xmlString.getBytes());
               deflaterOutputStream.close();
                   
               // Encoding the compressed message
               String encodedRequestMessage = Base64.encodeBytes(byteArrayOutputStream
                       .toByteArray(), Base64.DONT_BREAK_LINES);
               return encodedRequestMessage.trim();
               }*/

    /**
     * Decoding and deflating the encoded AuthReq
     *
     * @param encodedStr encoded AuthReq
     * @return decoded AuthReq
     */

    public static String decode(String encodedStr) throws Exception {
        return new String(Base64.decode(encodedStr));
    }
    /*@Deprecated
    public static String decode(String encodedStr) throws Exception {
    try {
        org.apache.commons.codec.binary.Base64 base64Decoder = new org.apache.commons.codec.binary.Base64();
        byte[] xmlBytes = encodedStr.getBytes("UTF-8");
        byte[] base64DecodedByteArray = base64Decoder.decode(xmlBytes);
        
        try {
            Inflater inflater = new Inflater(true);
            inflater.setInput(base64DecodedByteArray);
            byte[] xmlMessageBytes = new byte[5000];
            int resultLength = inflater.inflate(xmlMessageBytes);
        
            if (!inflater.finished()) {
                throw new RuntimeException("didn't allocate enough space to hold "
                        + "decompressed data");
            }
        
            inflater.end();
            return new String(xmlMessageBytes, 0, resultLength, "UTF-8");
        
        } catch (DataFormatException e) {
            ByteArrayInputStream bais = new ByteArrayInputStream(
                    base64DecodedByteArray);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            InflaterInputStream iis = new InflaterInputStream(bais);
            byte[] buf = new byte[1024];
            int count = iis.read(buf);
            while (count != -1) {
                baos.write(buf, 0, count);
                count = iis.read(buf);
            }
            iis.close();
        
            return new String(baos.toByteArray());
        }
    } catch (IOException e) {
        throw new Exception("Error when decoding the SAML Request.", e);
    }
        
    }*/

    /**
     * This method validates the signature of the SAML Response.
     *
     * @param resp SAML Response
     * @return true, if signature is valid.
     */
    public static boolean validateSignature(StatusResponseTypeImpl resp, String keyStoreName,
            String keyStorePassword, String alias, int tenantId, String tenantDomain) {
        if (resp.getSignature() == null) {
            log.error(
                    "SAML Response signing is enabled, but signature element not found in SAML Response element.");
            return false;
        }
        return validateSignature(resp.getSignature(), keyStoreName, keyStorePassword, alias, tenantId,
                tenantDomain);
    }

    public static boolean validateAssertionSignature(Response resp, String keyStoreName, String keyStorePassword,
            String alias, int tenantId, String tenantDomain) {
        Assertion assertion;
        assertion = retrieveAssertion(resp);
        if (assertion == null) {
            log.error("SAML Assertion not found in the Response");
            return false;
        }
        if (assertion.getSignature() == null) {
            log.error("SAMLAssertion signing is enabled, but signature element "
                    + "not found in SAML Assertion element.");
            return false;
        } else {
            return validateSignature(assertion.getSignature(), keyStoreName, keyStorePassword, alias, tenantId,
                    tenantDomain);
        }

    }

    private static boolean validateSignature(Signature signature, String keyStoreName, String keyStorePassword,
            String alias, int tenantId, String tenantDomain) {
        boolean isSigValid = false;
        try {
            KeyStore keyStore = null;
            java.security.cert.X509Certificate cert = null;
            if (tenantId != MultitenantConstants.SUPER_TENANT_ID) {
                // get an instance of the corresponding Key Store Manager instance
                KeyStoreManager keyStoreManager = KeyStoreManager.getInstance(tenantId);
                keyStore = keyStoreManager.getKeyStore(generateKSNameFromDomainName(tenantDomain));
                cert = (java.security.cert.X509Certificate) keyStore.getCertificate(tenantDomain);
            } else {
                keyStore = KeyStore.getInstance("JKS");
                keyStore.load(new FileInputStream(new File(keyStoreName)), keyStorePassword.toCharArray());
                cert = (java.security.cert.X509Certificate) keyStore.getCertificate(alias);
            }
            try {
                SAMLSignatureProfileValidator signatureProfileValidator = new SAMLSignatureProfileValidator();
                signatureProfileValidator.validate(signature);

                // Following code segment is taken from org.opensaml.security.SAMLSignatureProfileValidator
                // of OpenSAML 2.6.4. This is done to get the latest XSW related fixes.

                SignatureImpl sigImpl = (SignatureImpl) signature;
                XMLSignature apacheSig = sigImpl.getXMLSignature();
                SignableSAMLObject signableObject = (SignableSAMLObject) sigImpl.getParent();

                Reference ref = null;
                try {
                    ref = apacheSig.getSignedInfo().item(0);
                } catch (XMLSecurityException e) {
                    // This exception should never occur, because it's already checked
                    // from the previous call to signatureProfileValidator#validate
                    log.error("Apache XML Security exception obtaining Reference", e);
                    throw new ValidationException("Could not obtain Reference from Signature/SignedInfo", e);
                }

                String uri = ref.getURI();

                validateReferenceURI(uri, signableObject);
                validateObjectChildren(apacheSig);

                // End of OpenSAML 2.6.4 logic
            } catch (ValidationException e) {
                String logMsg = "Signature do not confirm to SAML signature profile. Possible XML Signature Wrapping "
                        + "Attack!";
                log.warn(logMsg);
                if (log.isDebugEnabled()) {
                    log.debug(logMsg, e);
                }
                return isSigValid;
            }

            X509CredentialImpl credentialImpl = new X509CredentialImpl(cert);
            SignatureValidator signatureValidator = new SignatureValidator(credentialImpl);
            signatureValidator.validate(signature);
            isSigValid = true;
            return isSigValid;
        } catch (Exception e) {
            log.error("Error while validating signature", e);
            return isSigValid;
        }
    }

    /**
     * Validates the 'Not Before' and 'Not On Or After' conditions of the SAML Assertion
     *
     * @param resp  SAML Response
     */
    public static boolean validateAssertionValidityPeriod(Response resp, Properties prop) {
        Assertion assertion;
        assertion = retrieveAssertion(resp);
        if (assertion == null) {
            log.error("SAML Assertion not found in the Response");
            return false;
        }
        DateTime validFrom = assertion.getConditions().getNotBefore();
        DateTime validTill = assertion.getConditions().getNotOnOrAfter();
        int timeStampSkewInSeconds = getTimeStampSkewInSeconds(prop);

        if (validFrom != null && validFrom.minusSeconds(timeStampSkewInSeconds).isAfterNow()) {
            log.error("Failed to meet SAML Assertion Condition 'Not Before'");
            return false;
        }

        if (validTill != null && validTill.plusSeconds(timeStampSkewInSeconds).isBeforeNow()) {
            log.error("Failed to meet SAML Assertion Condition 'Not On Or After'");
            return false;
        }

        if (validFrom != null && validTill != null && validFrom.isAfter(validTill)) {
            log.error(
                    "SAML Assertion Condition 'Not Before' must be less than the " + "value of 'Not On Or After'");
            return false;
        }
        return true;
    }

    /**
     * Validate the AudienceRestriction of SAML2 Response
     *
     * @param resp SAML response
     * @return validity
     */
    public static boolean validateAudienceRestriction(Response resp, Properties properties) {
        Assertion assertion;
        assertion = retrieveAssertion(resp);

        if (assertion == null) {
            log.error("SAML Assertion not found in the Response");
            return false;
        } else {
            Conditions conditions = assertion.getConditions();
            if (conditions != null) {
                List<AudienceRestriction> audienceRestrictions = conditions.getAudienceRestrictions();
                if (audienceRestrictions != null && !audienceRestrictions.isEmpty()) {
                    for (AudienceRestriction audienceRestriction : audienceRestrictions) {
                        if (CollectionUtils.isNotEmpty(audienceRestriction.getAudiences())) {
                            boolean audienceFound = false;
                            for (Audience audience : audienceRestriction.getAudiences()) {
                                if (properties.get(ISSUER).equals(audience.getAudienceURI())) {
                                    audienceFound = true;
                                    break;
                                }
                            }
                            if (!audienceFound) {
                                log.error("SAML Assertion Audience Restriction validation failed");
                                return false;
                            }
                        } else {
                            log.error("SAML Response's AudienceRestriction doesn't contain Audiences");
                            return false;
                        }
                    }
                } else {
                    log.error("SAML Response doesn't contain AudienceRestrictions");
                    return false;
                }
            } else {
                log.error("SAML Response doesn't contain Conditions");
                return false;
            }
        }
        return true;
    }

    private static int getTimeStampSkewInSeconds(Properties prop) {
        int timeStampSkewInSeconds = DEAFAULT_TIME_STAMP_SKEW_IN_SECONDS;
        if (prop != null && prop.containsKey(TIME_STAMP_SKEW)) {
            String timeStampSkew = prop.get(TIME_STAMP_SKEW).toString();
            if (timeStampSkew != null && timeStampSkew.length() > 0) {
                timeStampSkewInSeconds = Integer.parseInt(timeStampSkew);
            }
        }

        if (log.isDebugEnabled()) {
            log.debug("TimestampSkew is set to " + timeStampSkewInSeconds + " s.");
        }

        return timeStampSkewInSeconds;
    }

    private static Assertion retrieveAssertion(Response resp) {
        Assertion assertion = null;
        List<Assertion> assertions = resp.getAssertions();
        if (CollectionUtils.isNotEmpty(assertions)) {
            if (assertions.size() != 1) {
                log.error("SAML Response contains multiple assertions");
                return assertion;
            }
            assertion = assertions.get(0);
        }
        return assertion;
    }

    public static String getDomainName(XMLObject samlObject) {
        NodeList list = samlObject.getDOM().getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion",
                "NameID");
        String domainName = null;
        if (list.getLength() > 0) {
            String userName = list.item(0).getTextContent();
            domainName = MultitenantUtils.getTenantDomain(userName);
        }
        return domainName;
    }

    /**
     * Generate the key store name from the domain name
     *
     * @param tenantDomain tenant domain name
     * @return key store file name
     */
    private static String generateKSNameFromDomainName(String tenantDomain) {
        String ksName = tenantDomain.trim().replace(".", "-");
        return (ksName + ".jks");
    }

    /**
     * Create DocumentBuilderFactory with the XXE prevention measurements
     *
     * @return DocumentBuilderFactory instance
     */
    public static DocumentBuilderFactory getSecuredDocumentBuilder() {

        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        dbf.setXIncludeAware(false);
        dbf.setExpandEntityReferences(false);
        try {
            dbf.setFeature(Constants.SAX_FEATURE_PREFIX + Constants.EXTERNAL_GENERAL_ENTITIES_FEATURE, false);
            dbf.setFeature(Constants.SAX_FEATURE_PREFIX + Constants.EXTERNAL_PARAMETER_ENTITIES_FEATURE, false);
            dbf.setFeature(Constants.XERCES_FEATURE_PREFIX + Constants.LOAD_EXTERNAL_DTD_FEATURE, false);
        } catch (ParserConfigurationException e) {
            log.error("Failed to load XML Processor Feature " + Constants.EXTERNAL_GENERAL_ENTITIES_FEATURE + " or "
                    + Constants.EXTERNAL_PARAMETER_ENTITIES_FEATURE + " or " + Constants.LOAD_EXTERNAL_DTD_FEATURE);
        }

        SecurityManager securityManager = new SecurityManager();
        securityManager.setEntityExpansionLimit(ENTITY_EXPANSION_LIMIT);
        dbf.setAttribute(Constants.XERCES_PROPERTY_PREFIX + Constants.SECURITY_MANAGER_PROPERTY, securityManager);

        return dbf;
    }

    /** Build NameIDPolicy object given name ID policy format
    *
    * @param nameIdPolicy Name ID policy format
    * @return SAML NameIDPolicy object
    */
    public static NameIDPolicy buildNameIDPolicy(String nameIdPolicy) {
        NameIDPolicy nameIDPolicyObj = new NameIDPolicyBuilder().buildObject();
        if (!StringUtils.isEmpty(nameIdPolicy)) {
            nameIDPolicyObj.setFormat(nameIdPolicy);
        } else {
            nameIDPolicyObj.setFormat(SSOConstants.NAME_ID_POLICY_DEFAULT);
        }
        nameIDPolicyObj.setAllowCreate(true);
        return nameIDPolicyObj;
    }

    /** Build NameID object given name ID format
    *
    * @param nameIdFormat Name ID format
    * @param subject Subject
    * @return SAML NameID object
    */
    public static NameID buildNameID(String nameIdFormat, String subject) {
        NameID nameIdObj = new NameIDBuilder().buildObject();
        if (!StringUtils.isEmpty(nameIdFormat)) {
            nameIdObj.setFormat(nameIdFormat);
        } else {
            nameIdObj.setFormat(SSOConstants.NAME_ID_POLICY_DEFAULT);
        }
        nameIdObj.setValue(subject);
        return nameIdObj;
    }

    /**
     * Replaces the ${} in url with system properties and returns
     *
     * @param acsUrl assertion consumer service url
     * @return acsUrl with system properties replaced
     */
    public static String processAcsUrl(String acsUrl) {
        //matches shortest segments that are between '{' and '}'
        Pattern pattern = Pattern.compile("\\$\\{(.*?)\\}");
        Matcher matcher = pattern.matcher(acsUrl);
        while (matcher.find()) {
            String match = matcher.group(1);
            String property = System.getProperty(match);
            if (property != null) {
                acsUrl = acsUrl.replace("${" + match + "}", property);
            } else {
                log.warn("System Property " + match + " is not set");
            }
        }
        return acsUrl;
    }

    /**
     * Validate the Signature's Reference URI.
     *
     * First validate the Reference URI against the parent's ID itself.  Then validate that the
     * URI (if non-empty) resolves to the same Element node as is cached by the SignableSAMLObject.
     *
     *
     * @param uri the Signature Reference URI attribute value
     * @param signableObject the SignableSAMLObject whose signature is being validated
     * @throws ValidationException  if the URI is invalid or doesn't resolve to the expected DOM node
     */
    private static void validateReferenceURI(String uri, SignableSAMLObject signableObject)
            throws ValidationException {
        if (DatatypeHelper.isEmpty(uri)) {
            return;
        }

        String uriID = uri.substring(1);

        Element expected = signableObject.getDOM();
        if (expected == null) {
            log.error("SignableSAMLObject does not have a cached DOM Element.");
            throw new ValidationException("SignableSAMLObject does not have a cached DOM Element.");
        }
        Document doc = expected.getOwnerDocument();

        Element resolved = IdResolver.getElementById(doc, uriID);
        if (resolved == null) {
            log.error("Apache xmlsec IdResolver could not resolve the Element for id reference: " + uriID);
            throw new ValidationException(
                    "Apache xmlsec IdResolver could not resolve the Element for id reference: " + uriID);
        }

        if (!expected.isSameNode(resolved)) {
            log.error("Signature Reference URI " + uri + " did not resolve to the expected parent Element");
            throw new ValidationException("Signature Reference URI did not resolve to the expected parent Element");
        }
    }

    /**
     * Validate that the Signature instance does not contain any ds:Object children.
     *
     * @param apacheSig the Apache XML Signature instance
     * @throws ValidationException if the signature contains ds:Object children
     */
    private static void validateObjectChildren(XMLSignature apacheSig) throws ValidationException {
        if (apacheSig.getObjectLength() > 0) {
            log.error("Signature contained " + apacheSig.getObjectLength() + " ds:Object child element(s)");
            throw new ValidationException("Signature contained illegal ds:Object children");
        }
    }

}