eu.eidas.auth.engine.AbstractSAMLEngine.java Source code

Java tutorial

Introduction

Here is the source code for eu.eidas.auth.engine.AbstractSAMLEngine.java

Source

/* 
 * Licensed under the EUPL, Version 1.1 or  as soon they will be approved by
 * the European Commission - subsequent versions of the EUPL (the "Licence");
 * You may not use this work except in compliance with the Licence. You may
 * obtain a copy of the Licence at:
 * 
 * http://www.osor.eu/eupl/european-union-public-licence-eupl-v.1.1
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the Licence is distributed on an "AS IS" basis, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * Licence for the specific language governing permissions and limitations under
 * the Licence.
 */

package eu.eidas.auth.engine;

import java.io.ByteArrayInputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import eu.eidas.auth.commons.DocumentBuilderFactoryUtil;
import eu.eidas.auth.commons.EIDASErrors;
import eu.eidas.auth.commons.EIDASUtil;
import eu.eidas.auth.engine.core.SAMLEngineEncryptionI;
import eu.eidas.auth.engine.core.SAMLEngineSignI;
import eu.eidas.auth.engine.core.EIDASSAMLCore;
import eu.eidas.auth.engine.core.impl.EncryptionModuleFactory;
import eu.eidas.auth.engine.core.impl.SignModuleFactory;
import eu.eidas.auth.engine.metadata.MetadataProcessorI;
import eu.eidas.configuration.ConfigurationCreator;
import eu.eidas.configuration.ConfigurationReader;
import eu.eidas.configuration.InstanceEngine;
import eu.eidas.engine.exceptions.SAMLEngineException;
import eu.eidas.engine.exceptions.EIDASSAMLEngineException;
import eu.eidas.engine.exceptions.EIDASSAMLEngineRuntimeException;
import eu.eidas.samlengineconfig.CertificateConfigurationManager;

import org.opensaml.Configuration;
import org.opensaml.DefaultBootstrap;
import org.opensaml.common.SAMLObject;
import org.opensaml.common.SignableSAMLObject;
import org.opensaml.saml2.core.Response;
import org.opensaml.saml2.core.StatusCode;
import org.opensaml.saml2.metadata.EntitiesDescriptor;
import org.opensaml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml2.metadata.SSODescriptor;
import org.opensaml.xml.ConfigurationException;
import org.opensaml.xml.XMLObject;
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.UnmarshallerFactory;
import org.opensaml.xml.io.UnmarshallingException;
import org.opensaml.xml.parse.BasicParserPool;
import org.opensaml.xml.parse.XMLParserException;
import org.opensaml.xml.security.credential.Credential;
import org.opensaml.xml.signature.Signature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.apache.commons.lang.StringUtils;

/**
 * Class that wraps the operations over SAML tokens, both generation and
 * validation of SAML requests and SAML responses. Compliant with "OASIS Secure
 * Assertion Markup Language (SAML) 2.0, May 2005", but taking into account
 * EIDAS specific requirements.
 *
 * @author fjquevedo
 * @author iinigo
 */

public abstract class AbstractSAMLEngine {
    /*Dedicated marker for the SAML exchanges*/
    public static final Marker SAML_EXCHANGE = MarkerFactory.getMarker("SAML_EXCHANGE");
    /**
     * The instance of every engine SAML.
     */
    private static Map<String, Map<String, InstanceEngine>> instanceConfigs;

    /**
     * The instances of SAML engine.
     */
    private static Map<String, Map<String, Map<String, Object>>> instances;

    protected static final String DEFAULT_CONFIG_NAME = "default";

    /**
     * The logger.
     */
    private static final Logger LOG = LoggerFactory.getLogger(AbstractSAMLEngine.class.getName());

    /**
     * The Constant MODULE_SIGN_CONF.
     */
    private static final String MODULE_SIGN_CONF = "SignatureConf";

    /**
     * The Constant MODULE_ENCRYPTION_CONF.
     */
    private static final String MODULE_ENCRYPTION_CONF = "EncryptionConf";

    /**
     * The Constant SAML_ENGINE_SIGN_CLASS.
     */
    private static final String SAML_ENGINE_SIGN_CLASS = "class";

    /**
     * The Constant SAML_ENGINE_ENCRYPTION_CLASS.
     */
    private static final String SAML_ENGINE_ENCRYPTION_CLASS = "class";

    /**
     * The Constant SAML_ENGINE_CONF.
     */
    private static final String SAML_ENGINE_CONF = "SamlEngineConf";

    /**
     * The Constant SAML_ENGINE_FILE_CONF.
     */
    private static final String SAML_ENGINE_FILE_CONF = "fileConfiguration";

    /**
     * The codification of characters.
     */
    private static final String CHARACTER_ENCODING = "UTF-8";

    /**
     * The SAML core.
     */
    private EIDASSAMLCore samlCore;

    /**
     * The Module of Signature.
     */
    private SAMLEngineSignI signer;

    /**
     * The Module of Encryption.
     */
    private SAMLEngineEncryptionI cipher;

    /**
     * whether the response encryption is mandatory or not
     */
    private boolean mandatoryResponseEncryption = false;

    protected String instanceName;

    // See http://stackoverflow.com/questions/9828254/is-documentbuilderfactory-thread-safe-in-java-5
    // See also org.opensaml.xml.parse.ParserPool -- Code removed : private static DocumentBuilderFactory dbf = null
    /**
     * The Document Builder Factory.
     */
    private static final Queue<DocumentBuilderFactory> DOCUMENT_BUILDER_FACTORY_POOL = new ConcurrentLinkedQueue<DocumentBuilderFactory>();
    private static final Queue<DocumentBuilder> DOCUMENT_BUILDER_POOL = new ConcurrentLinkedQueue<DocumentBuilder>();

    private static final Queue<TransformerFactory> TRANSFORMER_FACTORY_POOL = new ConcurrentLinkedQueue<TransformerFactory>();
    private static final Queue<Transformer> TRANSFORMER_POOL = new ConcurrentLinkedQueue<Transformer>();

    //Country code used to encrypt the response sent
    private String countryRespondTo;
    //Country code used to decrypt the response received
    private String countryResponseFrom;
    private String requestIssuer;

    public String getRequestIssuer() {
        return requestIssuer;
    }

    public void setRequestIssuer(String requestIssuer) {
        this.requestIssuer = requestIssuer;
    }

    public static DocumentBuilderFactory newDocumentBuilderFactory() {
        try {
            return DocumentBuilderFactoryUtil.getSecureDocumentBuilderFactory();
        } catch (ParserConfigurationException e) {
            LOG.error("Error parser configuration in Load of documentBuilderFactory.");
            throw new EIDASSAMLEngineRuntimeException(e);
        }
    }

    /** Initializes the SAML engine. */
    /** Configure Document Builder Factory. */

    static {
        instanceConfigs = new HashMap<String, Map<String, InstanceEngine>>();
        instances = new HashMap<String, Map<String, Map<String, Object>>>();
    }

    /**
     * Method that initializes the basic services for the SAML Engine, like the
     * OpenSAML library and the BouncyCastle provider.
     */
    private static void startUp(String name, CertificateConfigurationManager configManager) {

        LOG.debug("SAMLEngine: Initialize OpenSAML instance=" + name);
        try {
            DefaultBootstrap.bootstrap();
        } catch (ConfigurationException e) {
            LOG.error("Problem initializing the OpenSAML library.");
            throw new EIDASSAMLEngineRuntimeException(e);
        }

        LOG.trace("Read all file configurations. (instances of SAMLEngine)");
        Map<String, InstanceEngine> engineInstances = instanceConfigs.get(name);
        if (null == engineInstances) {
            loadConfig(name, configManager);
        }

    }

    private static void loadConfig(String name, CertificateConfigurationManager configManager) {
        Map<String, InstanceEngine> engineInstances = null;
        try {
            synchronized (AbstractSAMLEngine.class) {
                if (configManager != null) {
                    engineInstances = ConfigurationReader.readConfiguration(configManager);
                }
                if (configManager == null || engineInstances == null || engineInstances.isEmpty()) {
                    engineInstances = ConfigurationReader.readConfiguration();
                }
                instanceConfigs.put(name, engineInstances);
            }
        } catch (SAMLEngineException e) {
            LOG.error("Error read configuration file.");
            throw new EIDASSAMLEngineRuntimeException(e);
        }
        LOG.debug("Create all instances of saml engine. (instances of SAMLEngine)");
        try {
            Map<String, Map<String, Object>> instanceParameters;
            if (configManager == null) {
                instanceParameters = ConfigurationCreator.createConfiguration(engineInstances);
            } else {
                instanceParameters = ConfigurationReader.getInstanceParameters(engineInstances, configManager);
            }
            instances.put(name, instanceParameters);
        } catch (EIDASSAMLEngineException e) {
            LOG.error("Error initializing instances from Eidas SAML engine.");
            throw new EIDASSAMLEngineRuntimeException(e);
        }

    }

    /**
     * Instantiates a new SAML engine.
     */
    private AbstractSAMLEngine() {

    }

    protected AbstractSAMLEngine(final String nameInstance) throws EIDASSAMLEngineException {
        this(nameInstance, DEFAULT_CONFIG_NAME, null);
    }

    /**
     * Instantiates a new SAML engine.
     *
     * @param nameInstance the name instance
     * @throws EIDASSAMLEngineException the EIDASSAML engine exception
     */
    protected AbstractSAMLEngine(final String nameInstance, final String configName,
            final CertificateConfigurationManager configManager) throws EIDASSAMLEngineException {
        this.instanceName = nameInstance;
        LOG.info("Loading Specific Configuration.");

        LOG.info("Create intance of saml messages for=" + nameInstance);
        if (!instances.containsKey(configName)) {
            synchronized (AbstractSAMLEngine.class) {
                if (!instances.containsKey(configName)) {
                    LOG.info("startUp=" + configName);
                    startUp(configName, configManager);
                }
            }
        } else if (instanceConfigs.get(configName).isEmpty()) {
            synchronized (AbstractSAMLEngine.class) {
                LOG.info("loadConfig=" + configName);
                loadConfig(configName, configManager);
            }
        }

        if (!instances.containsKey(configName)) {
            LOG.error("loading config " + configName);

            throw new EIDASSAMLEngineException(
                    EIDASUtil.getConfig(EIDASErrors.SAML_ENGINE_CONFIGURATION_ERROR.errorCode()),
                    EIDASUtil.getConfig(EIDASErrors.SAML_ENGINE_CONFIGURATION_ERROR.errorMessage()),
                    "loading config " + configName);
        }

        Map<String, Object> instance = instances.get(configName).get(nameInstance);

        if (instance == null || instance.isEmpty()) {
            LOG.error("Instance: " + nameInstance + " not exist.");
            throw new EIDASSAMLEngineException(
                    EIDASUtil.getConfig(EIDASErrors.SAML_ENGINE_CONFIGURATION_ERROR.errorCode()),
                    EIDASUtil.getConfig(EIDASErrors.SAML_ENGINE_CONFIGURATION_ERROR.errorMessage()),
                    "Instance: " + nameInstance + " not exist.");
        }

        Properties properties = (Properties) instance.get(SAML_ENGINE_CONF);

        if (properties == null) {
            LOG.error("SamlEngine.xml: not exist.");
            throw new EIDASSAMLEngineException(
                    EIDASUtil.getConfig(EIDASErrors.SAML_ENGINE_CONFIGURATION_ERROR.errorCode()),
                    EIDASUtil.getConfig(EIDASErrors.SAML_ENGINE_CONFIGURATION_ERROR.errorMessage()),
                    "SamlEngine.xml: not exist.");
        }

        samlCore = new EIDASSAMLCore(properties);

        final Map<String, Object> propertiesSign = (HashMap<String, Object>) instance.get(MODULE_SIGN_CONF);

        LOG.debug("Loading Module of sign.");
        signer = SignModuleFactory.getInstance(propertiesSign.get(SAML_ENGINE_SIGN_CLASS).toString());

        try {
            LOG.info("Initialize module of sign.");
            if (configManager == null) {
                LOG.info("1st module of sign source propriete="
                        + propertiesSign.get(SAML_ENGINE_FILE_CONF).toString());
                signer.init(propertiesSign.get(SAML_ENGINE_FILE_CONF).toString());
            } else {
                //the props are already read
                LOG.info("Else module of sign source propriete="
                        + propertiesSign.get(SAML_ENGINE_FILE_CONF).toString());
                signer.init((Properties) propertiesSign.get(SAML_ENGINE_FILE_CONF));
            }
            LOG.info("Load cryptographic service provider of module of sign.");
            signer.loadCryptServiceProvider();
        } catch (SAMLEngineException e) {
            LOG.error("Error create signature module: " + propertiesSign.get(SAML_ENGINE_FILE_CONF));
            LOG.info("Exception", e);
            throw new EIDASSAMLEngineException(EIDASErrors.SAML_ENGINE_CONFIGURATION_ERROR.errorCode(),
                    EIDASErrors.SAML_ENGINE_CONFIGURATION_ERROR.errorMessage(), e);
        }

        // LOADING ENCRYPTION CONFIGURATION
        final Map<String, Object> propertiesEncryption = (HashMap<String, Object>) instance
                .get(MODULE_ENCRYPTION_CONF);

        if (propertiesEncryption == null) {
            LOG.info("ERROR : Encryption module configuration not found. SAML Engine  '" + nameInstance
                    + "' in non-encryption mode!");
        } else {
            try {
                LOG.info("Loading Encryption for " + nameInstance);

                cipher = EncryptionModuleFactory
                        .getInstance(propertiesEncryption.get(SAML_ENGINE_ENCRYPTION_CLASS).toString());
                Object samlEngineConf = propertiesEncryption.get(SAML_ENGINE_FILE_CONF);
                if (samlEngineConf instanceof String) {
                    cipher.init((String) samlEngineConf);
                } else if (samlEngineConf instanceof Properties) {
                    cipher.init((Properties) samlEngineConf);
                } else {
                    LOG.error("Unknown configuration");
                }
            } catch (Exception e) {
                cipher = null;
                LOG.error("Encryption Module could not be loaded! SAML Engine '" + nameInstance
                        + "' in non-encryption mode!", e);
            }
        }
    }

    /**
     * Returns if the response should be encrypted
     *
     * @return true if encryption is on / false if encrytpion is off.
     */
    protected boolean encryptResponse() {
        return null != cipher && requestIssuer != null;
    }

    /**
     * Returns if the response should be decrypted
     *
     * @return
     */
    protected boolean decryptResponse() {
        return null != cipher ? cipher.isModuleEncryptionEnable() : false;
    }

    /**
     * Gets the Encrypter.
     */
    protected SAMLEngineEncryptionI getCipher() {
        return cipher;
    }

    /**
     * Gets the Signer properties.
     *
     * @return the SAML Sign properties
     */
    protected SAMLEngineSignI getSigner() {
        return signer;
    }

    public void setSignerProperty(String propName, String propValue) {
        if (signer != null && propName != null && propValue != null) {
            signer.setProperty(propName, propValue);
        } else {
            LOG.error(
                    "Configuration error - Unable to set signer property - signer {} propertyName {} value {} not set",
                    signer, propName, propValue);
        }
    }

    /**
     * Gets the SAML core properties.
     *
     * @return the SAML core properties
     */
    public final EIDASSAMLCore getSamlCoreProperties() {
        return samlCore;
    }

    /**
     * Method that transform the received SAML object into a byte array
     * representation.
     *
     * @param samlToken the SAML token.
     * @return the byte[] of the SAML token.
     * @throws SAMLEngineException the SAML engine exception
     */
    private byte[] marshall(final XMLObject samlToken) throws SAMLEngineException {

        try {
            final MarshallerFactory marshallerFactory = Configuration.getMarshallerFactory();

            final Marshaller marshaller = marshallerFactory.getMarshaller(samlToken);

            // See http://stackoverflow.com/questions/9828254/is-documentbuilderfactory-thread-safe-in-java-5
            DocumentBuilder documentBuilder = DOCUMENT_BUILDER_POOL.poll();
            if (documentBuilder == null) {
                DocumentBuilderFactory documentBuilderFactory = DOCUMENT_BUILDER_FACTORY_POOL.poll();
                if (documentBuilderFactory == null) {
                    documentBuilderFactory = newDocumentBuilderFactory();
                }
                documentBuilder = documentBuilderFactory.newDocumentBuilder();
                DOCUMENT_BUILDER_FACTORY_POOL.offer(documentBuilderFactory);
            }
            final Document doc = documentBuilder.newDocument();

            marshaller.marshall(samlToken, doc);

            // Obtain a byte array representation of the marshalled SAML object
            final DOMSource domSource = new DOMSource(doc);
            final StringWriter writer = new StringWriter();
            final StreamResult result = new StreamResult(writer);

            // See http://stackoverflow.com/questions/9828254/is-documentbuilderfactory-thread-safe-in-java-5
            Transformer transformer = TRANSFORMER_POOL.poll();
            if (transformer == null) {
                TransformerFactory transformerFactory = TRANSFORMER_FACTORY_POOL.poll();
                if (transformerFactory == null) {
                    transformerFactory = TransformerFactory.newInstance();
                }
                transformer = transformerFactory.newTransformer();
                TRANSFORMER_FACTORY_POOL.offer(transformerFactory);
            }

            transformer.transform(domSource, result);
            LOG.debug("SAML request \n" + writer.toString());
            return writer.toString().getBytes(CHARACTER_ENCODING);

        } catch (ParserConfigurationException e) {
            LOG.error("ParserConfigurationException.", e.getMessage());
            throw new SAMLEngineException(e);
        } catch (MarshallingException e) {
            LOG.info("ERROR : MarshallingException.", e.getMessage());
            throw new SAMLEngineException(e);
        } catch (TransformerConfigurationException e) {
            LOG.info("ERROR : TransformerConfigurationException.", e.getMessage());
            throw new SAMLEngineException(e);
        } catch (TransformerException e) {
            LOG.info("ERROR : TransformerException.", e.getMessage());
            throw new SAMLEngineException(e);
        } catch (UnsupportedEncodingException e) {
            LOG.error("ERROR : UnsupportedEncodingException: " + CHARACTER_ENCODING, e.getMessage());
            throw new SAMLEngineException(e);
        }
    }

    /**
     * Method that signs a SAML Token.
     *
     * @param tokenSaml the token SAML
     * @return the SAML object sign
     * @throws SAMLEngineException the SAML engine exception
     */
    protected SignableSAMLObject sign(final SignableSAMLObject tokenSaml, String messageFormat)
            throws SAMLEngineException {
        SignableSAMLObject tokenSamlToSign = tokenSaml;
        if (tokenSamlToSign instanceof Response) {
            // ENCRYPT THE SAMLObject BEFORE SIGN
            if (this.encryptResponse() && !SAMLEngineUtils.isErrorSamlResponse((Response) tokenSamlToSign)) {
                LOG.debug("Encryption Executing...");
                tokenSamlToSign = getCipher().encryptSAMLResponse((Response) tokenSamlToSign, getCountryRespondTo(),
                        getRequestIssuer(), messageFormat);
                LOG.debug("Encryption finished: " + tokenSamlToSign);
            } else if (!SAMLEngineUtils.isErrorSamlResponse((Response) tokenSamlToSign)) {
                checkUnencryptedResponsesAllowed();
            }
        }
        // SIGN
        LOG.debug("Sign SamlToken.");
        signer.sign(tokenSamlToSign);
        return tokenSamlToSign;
    }

    /**
     * check whether the unencrypted responses are allowed
     * @throws SAMLEngineException
     */
    private void checkUnencryptedResponsesAllowed() throws SAMLEngineException {
        if (isMandatoryResponseEncryption()) {
            throw new SAMLEngineException(EIDASErrors.SAML_ENGINE_UNENCRYPTED_RESPONSE.errorCode(),
                    EIDASErrors.SAML_ENGINE_UNENCRYPTED_RESPONSE.errorMessage());
        }
    }

    /**
     * Sign and transform to byte array.
     *
     * @param samlToken the SAML token
     * @return the byte[] of the SAML token
     * @throws SAMLEngineException the SAML engine exception
     */
    protected final byte[] signAndMarshall(final SignableSAMLObject samlToken, final String messageFormat)
            throws SAMLEngineException {
        LOG.debug("Marshall Saml Token.");
        SignableSAMLObject signElement = sign(samlToken, messageFormat);
        return marshall(signElement);
    }

    /**
     * Build the default set of parser features to use.
     * The default features set are:
     * <ul>
     * <li>{@link javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING} = true</li>
     * <li>http://apache.org/xml/features/disallow-doctype-decl = true</li>
     * Reference : https://www.owasp.org/index.php/XML_External_Entity_%28XXE%29_Processing
     * </ul>
     */
    protected static Map<String, Boolean> buildDefaultFeature() {
        Map<String, Boolean> features = new HashMap<String, Boolean>();
        features.put(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);

        // Ignore the external DTD completely
        // Note: this is for Xerces only:
        features.put("http://apache.org/xml/features/nonvalidating/load-external-dtd", Boolean.FALSE);
        // This is the PRIMARY defense. If DTDs (doctypes) are disallowed, almost all XML entity attacks are prevented
        // Xerces 2 only - http://xerces.apache.org/xerces2-j/features.html#disallow-doctype-decl
        features.put("http://apache.org/xml/features/disallow-doctype-decl", Boolean.TRUE);

        // If you can't completely disable DTDs, then at least do the following:
        // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities
        // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities
        features.put("http://xml.org/sax/features/external-general-entities", Boolean.FALSE);

        // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-parameter-entities
        // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities
        features.put("http://xml.org/sax/features/external-parameter-entities", Boolean.FALSE);

        return features;
    }

    public static BasicParserPool getNewBasicSecuredParserPool() {
        // Get parser pool manager
        BasicParserPool ppMgr = new BasicParserPool();
        // Note: this is necessary due to an unresolved Xerces deferred DOM issue/bug
        ppMgr.setBuilderFeatures(buildDefaultFeature());
        ppMgr.setNamespaceAware(true);
        return ppMgr;
    }

    private XMLObject performUnmarshall(final UnmarshallerFactory unmarshallerFact, final Element root)
            throws SAMLEngineException {
        final Unmarshaller unmarshaller = unmarshallerFact.getUnmarshaller(root);
        try {
            return unmarshaller.unmarshall(root);
        } catch (NullPointerException e) {
            LOG.info("ERROR : element tag incomplet or null.");
            throw new SAMLEngineException("NullPointerException", e);
        } catch (UnmarshallingException e) {
            LOG.info("ERROR : TransformerException.", e.getMessage());
            LOG.debug("ERROR : TransformerException.", e);
            throw new SAMLEngineException(e);
        }

    }

    /**
     * Method that unmarshalls a SAML Object from a byte array representation to
     * an XML Object.
     *
     * @param samlToken Byte array representation of a SAML Object
     * @return XML Object (superclass of SAMLObject)
     * @throws SAMLEngineException the SAML engine exception
     */
    protected final XMLObject unmarshall(final byte[] samlToken) throws SAMLEngineException {
        try {
            // Get parser pool manager
            final BasicParserPool ppMgr = getNewBasicSecuredParserPool();

            // Parse SAMLToken
            Document document = ppMgr.parse(new ByteArrayInputStream(samlToken));
            if (document != null) {
                final Element root = document.getDocumentElement();
                // Get appropriate unmarshaller
                final UnmarshallerFactory unmarshallerFact = Configuration.getUnmarshallerFactory();
                // Unmarshall using the SAML Token root element
                if (unmarshallerFact != null && root != null) {
                    return performUnmarshall(unmarshallerFact, root);
                } else {
                    LOG.info("ERROR : Error element tag incomplet or null.");
                    throw new SAMLEngineException("NullPointerException : unmarshallerFact or root is null");
                }
            } else {
                LOG.info("ERROR : Error element tag incomplet or null.");
                throw new SAMLEngineException("NullPointerException : document is null");
            }
        } catch (XMLParserException e) {
            LOG.info("XML Parsing Error.", e.getMessage());
            LOG.debug("XML Parsing Error.", e);
            throw new SAMLEngineException(e);
        }
    }

    /**
     * Method that validates an XML Signature contained in a SAML Token.
     *
     * @param samlToken the SAML token
     * @return the SAML object
     * @throws SAMLEngineException the SAML engine exception
     */
    protected final SAMLObject validateSignature(final SignableSAMLObject samlToken, String messageFormat)
            throws SAMLEngineException {

        LOG.debug("Validate Signature");
        signer.validateSignature(samlToken, messageFormat);

        SignableSAMLObject tokenSamlDecrypted = samlToken;
        if (this.decryptResponse() && samlToken instanceof Response
                && !((Response) samlToken).getEncryptedAssertions().isEmpty()) {
            // DECRYPT THE SAMLObject AFTER VALIDATION
            LOG.debug("Decryption Executing...");
            tokenSamlDecrypted = this.getCipher().decryptSAMLResponse((Response) samlToken,
                    getCountryResponseFrom());
            if (LOG.isTraceEnabled()) {
                LOG.trace("Decryption finished: "
                        + new String(marshall(tokenSamlDecrypted), Charset.defaultCharset()));
            } else {
                LOG.debug("Decryption finished.");
            }
        } else if (samlToken instanceof Response
                && (StatusCode.SUCCESS_URI.equals(((Response) samlToken).getStatus().getStatusCode().getValue()))) {
            checkUnencryptedResponsesAllowed();
        }
        return tokenSamlDecrypted;
    }

    protected final byte[] noSignAndMarshall(final SignableSAMLObject samlToken) throws SAMLEngineException {
        LOG.debug("Marshall Saml Token.");
        return marshall(samlToken);
    }

    public String getCountryRespondTo() {
        return countryRespondTo;
    }

    public void setCountryRespondTo(String countryRespondTo) {
        this.countryRespondTo = countryRespondTo;
    }

    public String getCountryResponseFrom() {
        return countryResponseFrom;
    }

    public void setCountryResponseFrom(String countryResponseFrom) {
        this.countryResponseFrom = countryResponseFrom;
    }

    public void setEncrypterProperty(String propName, String propValue) {
        if (cipher != null && propName != null && propValue != null) {
            cipher.setProperty(propName, propValue);
        } else {
            LOG.error(
                    "Configuration error - Unable to set encrypter property - cipher {} propertyName {} value {} not set",
                    cipher, propName, propValue);
        }
    }

    public Signature getSignature() throws SAMLEngineException {
        return signer == null ? null : signer.computeSignature(signer.getTrustStore());
    }

    public Credential getSigningCredential() throws SAMLEngineException {
        return signer == null ? null : signer.getPublicSigningCredential(signer.getTrustStore());
    }

    public Credential getEncryptionCredential() throws SAMLEngineException {
        return cipher == null ? null : cipher.getEncryptionCredential();
    }

    public void setMetadataProcessor(MetadataProcessorI processor) {
        signer.setMetadataProcessor(processor);
        if (cipher != null) {
            cipher.setMetadataProcessor(processor);
        }
    }

    public void signDescriptor(SSODescriptor descriptor) throws SAMLEngineException {
        signer.sign(descriptor);
    }

    public void signEntityDescriptor(EntityDescriptor descriptor) throws SAMLEngineException {
        signer.signMetadata(descriptor);
    }

    public byte[] signAndMarshallEntitiesDescriptor(EntitiesDescriptor descriptor) throws SAMLEngineException {
        signer.signMetadata(descriptor);
        return marshall(descriptor);

    }

    public boolean isMandatoryResponseEncryption() {
        return mandatoryResponseEncryption;
    }

    public void setMandatoryResponseEncryption(String mandatoryResponseEncryption) {
        if (cipher != null && StringUtils.isNotBlank(mandatoryResponseEncryption)) {
            this.mandatoryResponseEncryption = Boolean.parseBoolean(mandatoryResponseEncryption);
            cipher.setProperty(SAMLEngineEncryptionI.RESPONSE_ENCRYPTION_MANDATORY, mandatoryResponseEncryption);
        } else {
            LOG.error(
                    "Configuration error - Unable to set mandatory response encryption property - cipher is null or parameter mandatoryResponseEncryption {} is null",
                    cipher, mandatoryResponseEncryption);
        }
    }
}