org.asimba.idp.profile.catalog.saml2.SAML2Catalog.java Source code

Java tutorial

Introduction

Here is the source code for org.asimba.idp.profile.catalog.saml2.SAML2Catalog.java

Source

/*
 * Asimba Server
 * 
 * Copyright (C) 2013 Asimba
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see www.gnu.org/licenses
 * 
 * Asimba - Serious Open Source SSO - More information on www.asimba.org
 * 
 */
package org.asimba.idp.profile.catalog.saml2;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.asimba.idp.profile.catalog.AbstractCatalog;
import org.asimba.idp.profile.catalog.saml2.builder.CatalogEntitiesDescriptorBuilder;
import org.asimba.util.saml2.metadata.provider.MetadataProviderUtil;
import org.asimba.utility.xml.XMLUtils;
import org.opensaml.Configuration;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml2.common.Extensions;
import org.opensaml.saml2.metadata.ArtifactResolutionService;
import org.opensaml.saml2.metadata.AssertionConsumerService;
import org.opensaml.saml2.metadata.EntitiesDescriptor;
import org.opensaml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml2.metadata.IDPSSODescriptor;
import org.opensaml.saml2.metadata.KeyDescriptor;
import org.opensaml.saml2.metadata.NameIDFormat;
import org.opensaml.saml2.metadata.SPSSODescriptor;
import org.opensaml.saml2.metadata.SingleLogoutService;
import org.opensaml.saml2.metadata.SingleSignOnService;
import org.opensaml.saml2.metadata.impl.KeyDescriptorBuilder;
import org.opensaml.saml2.metadata.impl.SPSSODescriptorBuilder;
import org.opensaml.saml2.metadata.provider.MetadataProvider;
import org.opensaml.saml2.metadata.provider.MetadataProviderException;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.XMLObjectBuilderFactory;
import org.opensaml.xml.io.Marshaller;
import org.opensaml.xml.io.MarshallerFactory;
import org.opensaml.xml.io.MarshallingException;
import org.opensaml.xml.io.Unmarshaller;
import org.opensaml.xml.io.UnmarshallingException;
import org.opensaml.xml.parse.BasicParserPool;
import org.opensaml.xml.security.SecurityConfiguration;
import org.opensaml.xml.security.SecurityException;
import org.opensaml.xml.security.credential.UsageType;
import org.opensaml.xml.security.keyinfo.KeyInfoGenerator;
import org.opensaml.xml.security.keyinfo.KeyInfoGeneratorFactory;
import org.opensaml.xml.security.keyinfo.NamedKeyInfoGeneratorManager;
import org.opensaml.xml.security.x509.X509Credential;
import org.opensaml.xml.signature.KeyInfo;
import org.opensaml.xml.util.XMLObjectHelper;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.alfaariss.oa.OAException;
import com.alfaariss.oa.SystemErrors;
import com.alfaariss.oa.api.configuration.IConfigurationManager;
import com.alfaariss.oa.api.requestor.IRequestor;
import com.alfaariss.oa.engine.core.Engine;
import com.alfaariss.oa.engine.core.crypto.CryptoManager;
import com.alfaariss.oa.engine.core.idp.storage.IIDP;
import com.alfaariss.oa.util.saml2.SAML2Exchange;
import com.alfaariss.oa.util.saml2.SAML2Requestor;
import com.alfaariss.oa.util.saml2.crypto.SAML2CryptoUtils;
import com.alfaariss.oa.util.saml2.idp.SAML2IDP;

/**
 * SAML2 Catalog
 * 
 * Configuration example:
 * <catalog id="catalog.saml2" class="...SAML2Catalog">
 *   <mp_manager id="[id-value]" />
 *   <requestorsigning default="[true/false]" />
 *   ..
 * </catalog>
 * 
 * @author mdobrinic
 *
 */
public class SAML2Catalog extends AbstractCatalog {
    /** Configuration element names */
    public static final String EL_REQUESTORSIGNING = "requestorsigning";
    public static final String ATTR_DEFAULT = "default";
    public static final String EL_SAML2REFERENCES = "saml2_refs";
    public static final String EL_IDP_PROFILE = "idp_profile";
    public static final String EL_SP_METHOD = "sp_method";
    public static final String ATTR_ID = "id";
    public static final String EL_METADATA = "metadata";

    /** Local logger instance */
    private Log _oLogger;

    /**
     * Configurable setting to indicate default signing property 
     * of a SAML2Requestor
     * Default value: false 
     */
    protected boolean _bDefaultRequestorSigning;

    /**
     * Configurable SAML2 IDP Profile ID, to specify profile-scoped
     * IDP configuration
     * No default value, must be configured
     */
    protected String _sLinkedSAML2IDPProfileID;

    /**
     * Configurable SAML2 Authentication Method ID to specify
     * authmethod-scoped SP configuration
     * No default value, must be configured
     */
    protected String _sLinkedSAML2SPAuthenticationMethodID;

    /**
     * Configurable setting whether to enable LogoutService in
     * proxied catalog
     * Default value is false
     */
    protected boolean _bEnableProxiedLogoutService = false;

    /**
     * Configurable setting whether to enable ArtifactResolutionService in
     * proxied catalog
     * Default value is false
     */
    protected boolean _bEnableProxiedArtifactResolutionService = false;

    /**
     * Configurable id of a MetadataProviderManager that is responsible for 
     * managing the MetadataProviders for entities used by the SAML2Catalog
     * 
     * This is used as a reference only for SAML2 Requestor instantiation!
     */
    protected String _sMPMId;

    /** Locally maintained pool */
    protected BasicParserPool _oParserPool;

    /**
     * Default constructor
     */
    public SAML2Catalog() {
        super();
        _oLogger = LogFactory.getLog(SAML2Catalog.class);

        _oParserPool = new BasicParserPool();
        _oParserPool.setNamespaceAware(true);
        synchronized (this) {
            if (Configuration.getParserPool() == null) { // don't know why.. but cloning fails to work otherwise.
                Configuration.setParserPool(_oParserPool);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public void start(IConfigurationManager oConfigManager, Element eConfig) throws OAException {
        super.start(oConfigManager, eConfig);

        Element elSAML2Profile = oConfigManager.getSection(eConfig, EL_SAML2REFERENCES);
        if (elSAML2Profile == null) {
            _oLogger.error("Missing element '" + EL_SAML2REFERENCES + "' in SAML2Catalog configuration.");
            throw new OAException(SystemErrors.ERROR_CONFIG_READ);
        }

        readLinkedProfiles(oConfigManager, elSAML2Profile);

        _bDefaultRequestorSigning = false;
        Element elRequestorSigning = oConfigManager.getSection(eConfig, EL_REQUESTORSIGNING);
        if (elRequestorSigning != null) {
            String s = oConfigManager.getParam(elRequestorSigning, ATTR_DEFAULT);
            if (s != null) {
                if ("TRUE".equalsIgnoreCase(s)) {
                    _bDefaultRequestorSigning = true;
                } else {
                    if (!"FALSE".equalsIgnoreCase(s)) {
                        _oLogger.warn("Invalid value provided for '" + ATTR_DEFAULT + "' attribute for '"
                                + EL_REQUESTORSIGNING + "': " + s);
                    }

                    throw new OAException(SystemErrors.ERROR_CONFIG_READ);
                }
            }
        }

        // Establish MetadataProviderManager Id that refers to existing IMetadataProviderManager
        _sMPMId = null;
        Element elMPManager = oConfigManager.getSection(eConfig, MetadataProviderUtil.EL_MPM);
        if (elMPManager == null) {
            _oLogger.info("No '" + MetadataProviderUtil.EL_MPM + "'@'id' configured for catalog '" + _sID + "'; "
                    + "ensure that no SAML2Requestors are used in the catalog");
        } else {
            _sMPMId = oConfigManager.getParam(elMPManager, ATTR_ID);
            if (_sMPMId == null) {
                _oLogger.error("Missing @'" + ATTR_ID + "' attribute for '" + MetadataProviderUtil.EL_MPM
                        + "' configuration");
                throw new OAException(SystemErrors.ERROR_CONFIG_READ);
            }
            _oLogger.info("Using MetadataProviderManager Id from configuration: '" + _sMPMId + "'");
        }

        _oLogger.info("Started SAML2Catalog profile '" + getID() + "'");
    }

    /**
     * {@inheritDoc}
     */
    public void stop() {
        super.stop();
    }

    /**
     * Local helper to initialize the linked profiles
     * @param oConfigManager ConfigManager to use
     * @param eConfig Element containing the SP and IDP settings
     * @throws OAException When something goes really wrong
     */
    protected void readLinkedProfiles(IConfigurationManager oConfigManager, Element eConfig) throws OAException {
        _sLinkedSAML2IDPProfileID = null;

        Element eIDP = oConfigManager.getSection(eConfig, EL_IDP_PROFILE);
        if (eIDP == null) {
            _oLogger.error("No '" + EL_IDP_PROFILE + "' configured.");
            throw new OAException(SystemErrors.ERROR_CONFIG_READ);
        } else {
            _sLinkedSAML2IDPProfileID = oConfigManager.getParam(eIDP, ATTR_ID);
            if (_sLinkedSAML2IDPProfileID == null) {
                _oLogger.error("No '" + ATTR_ID + "' configured for '" + EL_IDP_PROFILE + "'.");
                throw new OAException(SystemErrors.ERROR_CONFIG_READ);
            }
        }

        _sLinkedSAML2SPAuthenticationMethodID = null;

        Element eSP = oConfigManager.getSection(eConfig, EL_SP_METHOD);
        if (eSP == null) {
            _oLogger.warn("No '" + EL_SP_METHOD + "' configured.");
        } else {
            _sLinkedSAML2SPAuthenticationMethodID = oConfigManager.getParam(eSP, ATTR_ID);
            if (_sLinkedSAML2SPAuthenticationMethodID == null) {
                _oLogger.error("No '" + ATTR_ID + "' configured for '" + EL_SP_METHOD + "'.");
                throw new OAException(SystemErrors.ERROR_CONFIG_READ);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public void service(HttpServletRequest oRequest, HttpServletResponse oResponse) throws OAException {
        // Prepare requestors first:
        List<IRequestor> lRequestors = getRequestors(oRequest); // low-cost

        // Prepare identity providers next:
        List<IIDP> lIDPs = getIDPs(oRequest);

        // Build catalog in EntitiesDescriptor
        CatalogEntitiesDescriptorBuilder oCatalogRoot = new CatalogEntitiesDescriptorBuilder(_oConfigManager,
                Engine.getInstance().getServer());

        // Establish our local EntityDescriptor that is the endpoint
        // on behalf of the proxied system
        EntityDescriptor oTheAsimbaEntityDescriptor = SAML2Exchange.getEntityDescriptor(_sLinkedSAML2IDPProfileID);

        // Add SP's
        for (IRequestor r : lRequestors) {
            SAML2Requestor s2req = getSAML2Requestor(r);

            // In transparant mode, we just echo the endpoints of the SP
            if (_sPublishMode.equals(PUBLISHMODE_TRANSPARANT)) {
                if (s2req == null) {
                    _oLogger.info("Skipping SP '" + r.getID() + "' in SAML2 Catalog because it is not a SAML2 SP");
                    continue;
                }

                EntityDescriptor oED = getTransparantSPEntityDescriptor(s2req);
                if (oED != null) {
                    oCatalogRoot.addEntityDescriptor(oED);
                }

                continue;
            }

            // In proxy-mode, we rewrite the endpoints to the linked SAML SP profile (authmethod)
            if (_sPublishMode.equals(PUBLISHMODE_PROXY)) {
                EntityDescriptor oED = getProxiedSPEntityDescriptor(r, oTheAsimbaEntityDescriptor);

                if (oED != null) {
                    oCatalogRoot.addEntityDescriptor(oED);
                }

                continue;
            }
        }

        // Add IDP's
        for (IIDP idp : lIDPs) {
            SAML2IDP oSAML2IDP = getSAML2IDP(idp);

            if (_sPublishMode.equals(PUBLISHMODE_TRANSPARANT)) {
                if (oSAML2IDP == null) {
                    _oLogger.warn(
                            "Skipping IDP '" + idp.getID() + "' in SAML2 Catalog because it is not a SAML2 IDP");
                    continue;
                }

                EntityDescriptor oED = getTransparantIDPEntityDescriptor(oSAML2IDP);
                if (oED != null) {
                    oCatalogRoot.addEntityDescriptor(oED);
                }

                continue;
            }

            // In proxy-mode, we rewrite the endpoints to the linked SAML SP profile (authmethod)
            if (_sPublishMode.equals(PUBLISHMODE_PROXY)) {
                EntityDescriptor oED = getProxiedIDPEntityDescriptor(idp, oTheAsimbaEntityDescriptor);

                if (oED != null) {
                    oCatalogRoot.addEntityDescriptor(oED);
                }

                continue;
            }
        }

        EntitiesDescriptor o = oCatalogRoot.getEntitiesDescriptor();

        // Now marshall this to XML
        try {
            MarshallerFactory marshallerFactory = Configuration.getMarshallerFactory();
            Marshaller marshaller = marshallerFactory.getMarshaller(o);
            Element e = marshaller.marshall(o);

            PrintWriter oPW = oResponse.getWriter();
            String s = XMLUtils.getStringFromDocument(e.getOwnerDocument());
            oPW.write(s);
            oPW.close();

            return;

        } catch (MarshallingException e) {
            _oLogger.error("Could not marshall EntitiesDescriptor catalog to DOM: " + e.getMessage());
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        } catch (IOException e) {
            _oLogger.error("Could not write output: " + e.getMessage());
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
    }

    protected EntityDescriptor getTransparantSPEntityDescriptor(SAML2Requestor oS2Req) {
        // In transparant mode, we just echo the endpoints of the SP
        // S2Requestor.metadataprovider is already managed
        MetadataProvider oMP = oS2Req.getMetadataProvider();

        if (oMP != null) {
            EntityDescriptor oED;
            try {
                oED = oMP.getEntityDescriptor(oS2Req.getID());
                if (_oLogger.isTraceEnabled())
                    _oLogger.trace("Adding SP '" + oS2Req.getID() + "' to catalog.");
                return oED;

            } catch (MetadataProviderException e) {
                _oLogger.warn("Could not retrieve metadata for '" + oS2Req.getID() + "'; omitting from catalog.");
            }
        } else {
            _oLogger.warn("Exclude requestor '" + oS2Req.getID() + "' from proxy-catalog, not a SAML2 SP");
        }

        return null;
    }

    protected EntityDescriptor getTransparantIDPEntityDescriptor(SAML2IDP oS2IDP) {
        MetadataProvider oMP = null;
        try {
            oMP = oS2IDP.getMetadataProvider();

            /* why managed????
            // Get MetadataProvider through Manager when possible
            if (_oMetadataProviderManager!=null) {
               oMP = _oMetadataProviderManager.getProviderFor(oS2IDP.getID(), null);
                   
               if (oMP == null) {
                  oMP = oS2IDP.getMetadataProvider();
                  _oMetadataProviderManager.setProviderFor(oS2IDP.getID(), oMP, null);
               }
            }
            */

            if (oMP != null) {
                EntityDescriptor oED;
                oED = oMP.getEntityDescriptor(oS2IDP.getID());

                if (_oLogger.isTraceEnabled())
                    _oLogger.trace("Adding IDP '" + oS2IDP.getID() + "' to catalog.");

                return oED;
            }
        } catch (OAException e) {
            _oLogger.warn(
                    "Could not retrieve metadataprovider for IDP '" + oS2IDP.getID() + "': " + e.getMessage());
        } catch (MetadataProviderException e) {
            _oLogger.warn("Could not retrieve metadata for IDP '" + oS2IDP.getID() + "': " + e.getMessage());
        }

        _oLogger.warn("Exclude IDP '" + oS2IDP.getID() + "' from proxy-catalog, metadata is not available.");
        return null;
    }

    /**
     * Create a proxied SP EntityDescriptor<br/>
     * This EntityDescriptor contains the EntityID of the supplied Requestor, but
     * the ACS URLs are rewritten, so they are routed through this Asimba SAML2 SP
     * 
     * Whenever an (external) IDP wants to deliver an identity for a service, it can do
     * so to this (external) SP ACS URL; this means that Asimba can act on behalf of
     * other SP's. This can be relevant when translating protocols with being as invisible
     * as possible.
     * Are there other use cases? Probably. 
     * 
     * <b>note</b> This requires Shadow-SP mode to be enabled for the SAML2 AuthMethod 
     * to make it actually work.
     * 
     * @param oRequestor
     * @param oTheAsimbaEntityDescriptor 
     * @return
     * @throws OAException
     */
    protected EntityDescriptor getProxiedSPEntityDescriptor(IRequestor oRequestor,
            EntityDescriptor oTheAsimbaEntityDescriptor) throws OAException {
        // Prepare to build
        XMLObjectBuilderFactory oBuilder = Configuration.getBuilderFactory();

        SPSSODescriptor oTheAsimbaSPSSODescriptor = oTheAsimbaEntityDescriptor
                .getSPSSODescriptor(SAMLConstants.SAML20P_NS);

        // 1. Get EntityDescriptorBuilder (opensaml class!)
        org.opensaml.saml2.metadata.impl.EntityDescriptorBuilder oBuilder_ED = (org.opensaml.saml2.metadata.impl.EntityDescriptorBuilder) oBuilder
                .getBuilder(EntityDescriptor.DEFAULT_ELEMENT_NAME);

        EntityDescriptor oED_publish = oBuilder_ED.buildObject();

        // Set main properties:
        oED_publish.setEntityID(oRequestor.getID());

        // 2. Get RoleDescriptorBuilder for SPSSODescriptor:
        SPSSODescriptorBuilder oBuilder_SPSSO = (SPSSODescriptorBuilder) oBuilder
                .getBuilder(SPSSODescriptor.DEFAULT_ELEMENT_NAME);

        SPSSODescriptor oSPSSO_publish = oBuilder_SPSSO.buildObject();
        oSPSSO_publish.addSupportedProtocol(SAMLConstants.SAML20P_NS);

        // 2.1. Copy some SPSSO attributes:
        if (oTheAsimbaSPSSODescriptor.getWantAssertionsSigned()) {
            oSPSSO_publish.setWantAssertionsSigned(true);
        }

        // 2.2. Add our LOCAL bindings for receiving the response:
        try {
            List<AssertionConsumerService> lACS = oTheAsimbaSPSSODescriptor.getAssertionConsumerServices();
            for (AssertionConsumerService oACS : lACS) {
                AssertionConsumerService oACS_new = (AssertionConsumerService) XMLObjectHelper.cloneXMLObject(oACS,
                        true);

                // 2.2.1. Consider: adding (shadowed?) SP-context to the endpoints to recognize on
                // behalf of which SP the ACS URL is being requested?
                oSPSSO_publish.getAssertionConsumerServices().add(oACS_new);
            }
        } catch (MarshallingException e) {
            _oLogger.warn("Could not add SP '" + oRequestor.getID() + "'; due to marshalling problem with ACS.");
            return null;
        } catch (UnmarshallingException e) {
            _oLogger.warn("Could not add SP '" + oRequestor.getID() + "'; due to unmarshalling problem with ACS.");
            return null;
        }

        // 2.3. Add our LOCAL signing key
        KeyDescriptor oKD = getSigningKeyDescriptor(oBuilder, Engine.getInstance().getCryptoManager(),
                oRequestor.getID());

        if (oKD != null) {
            oSPSSO_publish.getKeyDescriptors().add(oKD);
        }

        // 2.5. Add results
        oED_publish.getRoleDescriptors().add(oSPSSO_publish);

        // 2.6. Add to catalog
        return oED_publish;
    }

    /**
     * Create a proxied IDP EntityDescriptor<br/>
     * This EntityDescriptor contains the EntityID of the supplied IDP, but
     * the endpoints are rewritten, so they are routed through this Asimba SAML2 IDP<br/>
     * 
     * Supports:<br/>
     * <ul>
     * <li>NameIDFormat from Asimba SAML2 IDP</li>
     * <li>SingleSignOnService, SingleLogoutService, ArtifactResolutionService from SAML2 IDP</li>
     * </ul>
     * 
     * The reference that is added to the SSO/SLO/AR endpoints, is encoded like:
     * [endpoint]/i=[sha1-hash-of-entity-id||lowercase-hexstring-encoded]
     * Example (for EntityID = '12345' (without the quotes)):
     * https://www.asimba.org/profiles/saml2/sso/web/i=2672275fe0c456fb671e4f417fb2f9892c7573ba
     * 
     * <b>note</b> Requires ShadowIDP support to be enabled in the SAML2 IDP Profile!
     * 
     * @param oIDP
     * @param oTheAsimbaEntityDescriptor
     * @return
     * @throws OAException
     */
    protected EntityDescriptor getProxiedIDPEntityDescriptor(IIDP oIDP, EntityDescriptor oTheAsimbaEntityDescriptor)
            throws OAException {
        // Prepare to build
        XMLObjectBuilderFactory oBuilder = Configuration.getBuilderFactory();

        IDPSSODescriptor oTheAsimbaIDPSSODescriptor = oTheAsimbaEntityDescriptor
                .getIDPSSODescriptor(SAMLConstants.SAML20P_NS);

        // 1. Get EntityDescriptorBuilder (opensaml class!)
        org.opensaml.saml2.metadata.impl.EntityDescriptorBuilder oBuilder_ED = (org.opensaml.saml2.metadata.impl.EntityDescriptorBuilder) oBuilder
                .getBuilder(EntityDescriptor.DEFAULT_ELEMENT_NAME);

        EntityDescriptor oED_publish = oBuilder_ED.buildObject();

        // Set main properties:
        oED_publish.setEntityID(oIDP.getID());

        // 2. Get RoleDescriptorBuilder for IDPSSODescriptor:
        org.opensaml.saml2.metadata.impl.IDPSSODescriptorBuilder oBuilder_IDPSSO = (org.opensaml.saml2.metadata.impl.IDPSSODescriptorBuilder) oBuilder
                .getBuilder(IDPSSODescriptor.DEFAULT_ELEMENT_NAME);

        IDPSSODescriptor oIDPSSO_publish = oBuilder_IDPSSO.buildObject();
        oIDPSSO_publish.addSupportedProtocol(SAMLConstants.SAML20P_NS);

        // 3. Copy some local properties:
        if (oTheAsimbaIDPSSODescriptor.getWantAuthnRequestsSigned()) {
            oIDPSSO_publish.setWantAuthnRequestsSigned(true);
        }

        // 3.1. Copy NameIDFormat from Asimba's config:
        try {
            List<NameIDFormat> l = oTheAsimbaIDPSSODescriptor.getNameIDFormats();
            if (l != null) {
                for (NameIDFormat nf : l) {
                    NameIDFormat oNF_new;
                    // oNF_new = (NameIDFormat) cloneXMLObject_usingDOM(nf);
                    // oNF_new = (NameIDFormat) cloneXMLObject(nf);
                    oNF_new = (NameIDFormat) XMLObjectHelper.cloneXMLObject(nf, true);

                    oIDPSSO_publish.getNameIDFormats().add(oNF_new);
                }
            }
        } catch (MarshallingException e) {
            _oLogger.warn(
                    "Could not add IDP '" + oIDP.getID() + "'; due to marshalling problem with NameIDFormat.");
            return null;
        } catch (UnmarshallingException e) {
            _oLogger.warn(
                    "Could not add IDP '" + oIDP.getID() + "'; due to unmarshalling problem with NameIDFormat.");
            return null;
        }

        String sShadowIDPAlias = DigestUtils.shaHex(oIDP.getID());

        // 3.2. Copy (and remap?) SingleSignOnService, SingleLogoutService, ArtifactResolutionService endpoints
        try {
            List<SingleSignOnService> lsso = oTheAsimbaIDPSSODescriptor.getSingleSignOnServices();
            if (lsso != null) {
                for (SingleSignOnService ssos : lsso) {
                    SingleSignOnService oSSOS_new;
                    // oSSOS_new = (SingleSignOnService) cloneXMLObject_usingDOM(ssos);
                    oSSOS_new = (SingleSignOnService) XMLObjectHelper.cloneXMLObject(ssos, true);

                    // Rewrite endpoint to include entityid-reference:
                    String sEndpoint = ssos.getLocation();
                    sEndpoint = sEndpoint + "/i=" + sShadowIDPAlias;
                    oSSOS_new.setLocation(sEndpoint);

                    oIDPSSO_publish.getSingleSignOnServices().add(oSSOS_new);
                }
            }

            if (_bEnableProxiedLogoutService) {
                List<SingleLogoutService> lsl = oTheAsimbaIDPSSODescriptor.getSingleLogoutServices();
                if (lsl != null) {
                    for (SingleLogoutService sls : lsl) {
                        SingleLogoutService oSLS_new;
                        // oSLS_new = (SingleLogoutService) cloneXMLObject_usingDOM(sls);
                        oSLS_new = (SingleLogoutService) XMLObjectHelper.cloneXMLObject(sls, true);

                        // Rewrite endpoint to include entityid-reference:
                        String sEndpoint = sls.getLocation();
                        sEndpoint = sEndpoint + "/i=" + sShadowIDPAlias;
                        oSLS_new.setLocation(sEndpoint);

                        oIDPSSO_publish.getSingleLogoutServices().add(oSLS_new);
                    }
                }
            }

            if (_bEnableProxiedArtifactResolutionService) {
                List<ArtifactResolutionService> lars = oTheAsimbaIDPSSODescriptor.getArtifactResolutionServices();
                if (lars != null) {
                    for (ArtifactResolutionService ars : lars) {
                        ArtifactResolutionService oARS_new;
                        // oARS_new = (ArtifactResolutionService) cloneXMLObject_usingDOM(ars);
                        oARS_new = (ArtifactResolutionService) XMLObjectHelper.cloneXMLObject(ars, true);

                        // Rewrite endpoint to include entityid-reference:
                        String sEndpoint = ars.getLocation();
                        sEndpoint = sEndpoint + "/i=" + sShadowIDPAlias;
                        oARS_new.setLocation(sEndpoint);

                        oIDPSSO_publish.getArtifactResolutionServices().add(oARS_new);
                    }
                }
            }

        } catch (MarshallingException e) {
            _oLogger.warn("Could not add IDP '" + oIDP.getID() + "'; due to marshalling problem with Services.");
            return null;
        } catch (UnmarshallingException e) {
            _oLogger.warn("Could not add IDP '" + oIDP.getID() + "'; due to unmarshalling problem with Services.");
            return null;
        }

        // 3.3. Copy <extensions> when they exist 
        try {
            Extensions ext = oTheAsimbaIDPSSODescriptor.getExtensions();
            if (ext != null) {
                // Extensions oExt_new = (Extensions) cloneXMLObject_usingDOM(ext);
                Extensions oExt_new = (Extensions) XMLObjectHelper.cloneXMLObject(ext, true);
                oIDPSSO_publish.setExtensions(oExt_new);
            }
        } catch (MarshallingException e) {
            _oLogger.warn("Could not add IDP '" + oIDP.getID() + "'; due to marshalling problem with Extensions.");
            return null;
        } catch (UnmarshallingException e) {
            _oLogger.warn(
                    "Could not add IDP '" + oIDP.getID() + "'; due to unmarshalling problem with Extensions.");
            return null;
        }

        // 3.4. Add our LOCAL signing key
        KeyDescriptor oKD = getSigningKeyDescriptor(oBuilder, Engine.getInstance().getCryptoManager(),
                oIDP.getID());

        if (oKD != null) {
            oIDPSSO_publish.getKeyDescriptors().add(oKD);
        }

        // 3.5. Add results
        oED_publish.getRoleDescriptors().add(oIDPSSO_publish);

        // 3.6. Add to catalog
        return oED_publish;
    }

    /**
     * Create a new KeyDescriptor instance based on Asimba Engine's crypto
     * configuration settings
     *  
     * @param oBuilder Initialized OpenSAML XMLObjectBuilderFactory to use
     * @param oCrypto Configured Asimba CryptoManager
     * @param sEntityID EntityID used to publish specific signing credentials in KeyDescriptor (?)
     * @return
     * @throws OAException
     */
    public KeyDescriptor getSigningKeyDescriptor(XMLObjectBuilderFactory oBuilder, CryptoManager oCrypto,
            String sEntityID) throws OAException {
        try {
            //Build signing key descriptor
            KeyDescriptorBuilder oKeyDescriptorBuilder = (KeyDescriptorBuilder) oBuilder
                    .getBuilder(KeyDescriptor.DEFAULT_ELEMENT_NAME);
            KeyDescriptor oKeyDescriptor = oKeyDescriptorBuilder.buildObject();

            oKeyDescriptor.setUse(UsageType.SIGNING);

            //Build credential
            X509Credential signingCredential = SAML2CryptoUtils.retrieveMySigningCredentials(oCrypto, sEntityID);

            // Using default: Configuration.getGlobalSecurityConfiguration and XMLSignature
            SecurityConfiguration oSecConfig = Configuration.getGlobalSecurityConfiguration();
            NamedKeyInfoGeneratorManager oKIGMgr = oSecConfig.getKeyInfoGeneratorManager();
            KeyInfoGeneratorFactory oKIGFactory = oKIGMgr.getDefaultManager().getFactory(signingCredential);

            KeyInfoGenerator kiGenerator = oKIGFactory.newInstance();
            if (kiGenerator != null) {
                KeyInfo keyInfo = kiGenerator.generate(signingCredential);
                oKeyDescriptor.setKeyInfo(keyInfo);
            }

            return oKeyDescriptor;
        } catch (SecurityException e) {
            _oLogger.error("Could not generate SigningKeyDescriptor", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
    }

    /**
     * Instantiate a new SAML2Requestor from a IRequestor instance
     * Uses IRequestor-properties for the extra information
     * @param r
     * @return
     * @throws OAException
     */
    private SAML2Requestor getSAML2Requestor(IRequestor r) throws OAException {
        SAML2Requestor o = null;

        try {
            // The Linked Profile ID is used to lookup profile-specific 
            // requestor-properties by the SAML2Requestor initialization procedure
            o = new SAML2Requestor(r, _bDefaultRequestorSigning, _sLinkedSAML2IDPProfileID, _sMPMId);

        } catch (OAException e) {
            _oLogger.error("Could not create SAML2Requestor for requestor '" + r.getID() + "'");
        }

        return o;
    }

    /**
     * Returns a SAML2IDP version of the IIDP or null if the IDP was no SAML2 IDP
     * @param i the IIDP instance
     * @return
     * @throws OAException
     */
    private SAML2IDP getSAML2IDP(IIDP i) {
        if (!(i instanceof SAML2IDP))
            return null;

        SAML2IDP oSAML2IDP = (SAML2IDP) i;
        return oSAML2IDP;
    }

    private <T extends XMLObject> T cloneXMLObject_usingDOM(XMLObject oSource)
            throws MarshallingException, UnmarshallingException {
        try {

            Element elSource = oSource.getDOM();

            // Element elSource = oMarshaller.marshall(oSource, oDocument);   // is this right: .getDOM() ??

            Element clonedElement = (Element) elSource.cloneNode(true); // deep clone

            Unmarshaller unmarshaller = Configuration.getUnmarshallerFactory().getUnmarshaller(clonedElement);
            T clonedXMLObject = (T) unmarshaller.unmarshall(clonedElement);

            return clonedXMLObject;
        } catch (UnmarshallingException e) {
            _oLogger.warn("Could not unmarshall element '" + oSource.getElementQName() + "'");
            return null;
        } finally {
        }
    }

    /**
     * Private helper to clone XMLObjects
     * @param oSource
     * @return
     * @throws OAException
     */
    private <T extends XMLObject> T cloneXMLObject(XMLObject oSource)
            throws MarshallingException, UnmarshallingException {
        Marshaller oMarshaller = Configuration.getMarshallerFactory().getMarshaller(oSource);
        try {
            if (oMarshaller == null) {
                _oLogger.warn("Unknown element '" + oSource.getElementQName() + "'; no marshaller available");
                return null;
            }

            // go through process of creating a new Document as intermediate:
            DocumentBuilderFactory oDBFactory = DocumentBuilderFactory.newInstance();
            oDBFactory.setNamespaceAware(true);
            DocumentBuilder oDocBuilder = oDBFactory.newDocumentBuilder();
            DOMImplementation oDOMImpl = oDocBuilder.getDOMImplementation();
            Document oDocument = oDOMImpl.createDocument(null, null, null);

            Element elSource = oSource.getDOM();

            // Element elSource = oMarshaller.marshall(oSource, oDocument);   // is this right: .getDOM() ??

            Element clonedElement = (Element) elSource.cloneNode(true); // deep clone

            Unmarshaller unmarshaller = Configuration.getUnmarshallerFactory().getUnmarshaller(clonedElement);
            T clonedXMLObject = (T) unmarshaller.unmarshall(clonedElement);

            return clonedXMLObject;

            //      } catch (MarshallingException e) {
            //         _oLogger.warn("Could not marshall element '"+oSource.getElementQName()+"'");
            //         return null;
        } catch (UnmarshallingException e) {
            _oLogger.warn("Could not unmarshall element '" + oSource.getElementQName() + "'");
            return null;
        } catch (ParserConfigurationException e) {
            _oLogger.warn("Exception when creating intermedia document for cloning: " + e.getMessage());
            return null;
        }

    }

}