org.aselect.server.request.handler.xsaml20.SamlTools.java Source code

Java tutorial

Introduction

Here is the source code for org.aselect.server.request.handler.xsaml20.SamlTools.java

Source

/*
 * * Copyright (c) Anoigo. All rights reserved.
 *
 * A-Select is a trademark registered by SURFnet bv.
 *
 * This program is distributed under the EUPL 1.0 (http://osor.eu/eupl)
 * See the included LICENSE file for details.
 *
 * If you did not receive a copy of the LICENSE
 * please contact Anoigo. (http://www.anoigo.nl) 
 */
package org.aselect.server.request.handler.xsaml20;

import java.io.IOException;
import java.io.PrintWriter;

import java.net.URLDecoder;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.interfaces.RSAPublicKey;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.StringTokenizer;
import java.util.logging.Level;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.aselect.server.config.ASelectConfigManager;
import org.aselect.server.crypto.CryptoEngine;
import org.aselect.server.log.ASelectSystemLogger;
import org.aselect.server.request.handler.xsaml20.SamlTools;
import org.aselect.system.error.Errors;
import org.aselect.system.exception.ASelectException;
import org.aselect.system.utils.Utils;
import org.joda.time.DateTime;
import org.opensaml.Configuration;
import org.opensaml.common.SAMLObject;
import org.opensaml.common.SAMLObjectBuilder;
import org.opensaml.common.SAMLVersion;
import org.opensaml.common.SignableSAMLObject;
import org.opensaml.common.impl.SAMLObjectContentReference;
import org.opensaml.saml2.core.Assertion;
import org.opensaml.saml2.core.AudienceRestriction;
import org.opensaml.saml2.core.AuthnRequest;
import org.opensaml.saml2.core.AuthnStatement;
import org.opensaml.saml2.core.Conditions;
import org.opensaml.saml2.core.Issuer;
import org.opensaml.saml2.core.LogoutRequest;
import org.opensaml.saml2.core.LogoutResponse;
import org.opensaml.saml2.core.NameID;
import org.opensaml.saml2.core.NameIDType;
import org.opensaml.saml2.core.SessionIndex;
import org.opensaml.saml2.core.Status;
import org.opensaml.saml2.core.StatusCode;
import org.opensaml.saml2.core.SubjectConfirmationData;
import org.opensaml.security.SAMLSignatureProfileValidator;
import org.opensaml.ws.message.decoder.MessageDecodingException;
import org.opensaml.ws.message.encoder.MessageEncodingException;
import org.opensaml.ws.transport.http.HttpServletResponseAdapter;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.XMLObjectBuilderFactory;
import org.opensaml.xml.encryption.EncryptionConstants;
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.security.credential.BasicCredential;
import org.opensaml.xml.security.keyinfo.KeyInfoHelper;
import org.opensaml.xml.signature.KeyInfo;
import org.opensaml.xml.signature.Signature;
import org.opensaml.xml.signature.SignatureConstants;
import org.opensaml.xml.signature.SignatureException;
import org.opensaml.xml.signature.SignatureValidator;
import org.opensaml.xml.signature.Signer;
import org.opensaml.xml.signature.X509Certificate;
import org.opensaml.xml.signature.impl.KeyInfoBuilder;
import org.opensaml.xml.signature.impl.SignatureBuilder;
import org.opensaml.xml.util.Base64;
import org.opensaml.xml.util.XMLConstants;
import org.opensaml.xml.util.XMLHelper;
import org.opensaml.xml.validation.ValidationException;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class SamlTools {
    private static final String MODULE = "SamlTools";

    // 20091203, generate our own id
    /**
     * Generate identifier.
     * 
     * @param systemLogger
     *            the system logger
     * @param sModule
     *            the module
     * @return the string
     * @throws ASelectException
     */
    public static String generateIdentifier(ASelectSystemLogger systemLogger, String sModule)
            throws ASelectException {
        String sMethod = "generateIdentifier";
        byte[] baRandomBytes = new byte[20];

        CryptoEngine.nextRandomBytes(baRandomBytes);
        return "I" + Utils.byteArrayToHexString(baRandomBytes);
    }

    /**
     * Helper method to detect if the HttpServletRequest is signed The HttpServletRequest is signed if:
     * <ul>
     * <li>There is a parameter 'SigAlg' witch contains the value 'http://www.w3.org/2000/09/xmldsig#'</li>
     * <li><b>And</b> there is a parameter 'Signature'</li>
     * </ul>
     * 
     * @param httpRequest
     *            the http request
     * @return boolean
     */
    @SuppressWarnings("unchecked")
    public static boolean isSigned(HttpServletRequest httpRequest) {
        Enumeration<String> enumParameterNames = httpRequest.getParameterNames();

        boolean bSigAlg = false;
        boolean bSignature = false;

        while (enumParameterNames.hasMoreElements() && (!bSigAlg || !bSignature)) {
            String sParameterName = enumParameterNames.nextElement();
            if (!bSigAlg)
                bSigAlg = (httpRequest.getParameter(sParameterName).contains(XMLConstants.XMLSIG_NS)) // Backward compatibility, 
                        //though should be equal to SignatureConstants.ALGO_ID_DIGEST_SHA1 
                        // RH, 20140310, sn
                        || (httpRequest.getParameter(sParameterName)
                                .contains(SignatureConstants.ALGO_ID_DIGEST_SHA1))
                        || (httpRequest.getParameter(sParameterName)
                                .contains(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256));
            // RH, 20140310, en
            if (!bSignature)
                bSignature = sParameterName.equals(Signature.DEFAULT_ELEMENT_LOCAL_NAME);
        }
        return bSigAlg && bSignature;
    }

    /**
     * Helper method to verify the Signature of a HTTP GET request.
     * 
     * @param key
     *            PublicKey
     * @param httpRequest
     *            HttpServletRequest
     * @return boolean
     * @throws MessageDecodingException
     *             the message decoding exception
     */
    public static boolean verifySignature(PublicKey key, HttpServletRequest httpRequest)
            throws MessageDecodingException {
        String sMethod = "verifySignature";
        ASelectSystemLogger systemLogger = ASelectSystemLogger.getHandle();
        systemLogger.log(Level.INFO, MODULE, sMethod, "Public key=" + key + " hashcode=" + key.hashCode());

        String signingAlgo;
        if (key instanceof RSAPublicKey) {
            signingAlgo = "SHA1withRSA";
        } else {
            signingAlgo = "SHA1withDSA";
        }

        // The signature was set on the complete query string, except the Signature parameter
        // RH, 20140310, Now following SAML specs
        try {
            String sQuery = httpRequest.getQueryString();// use query string because original urlencoded values must be used
            // do not url re-encode because url-encoding is not canonical
            // (other party may have different, though equally valid, url-encoding algoritm
            StringTokenizer tokenizer = new StringTokenizer(sQuery, "&");
            String sData = "";
            String[] verifyData = new String[3];
            while (tokenizer.hasMoreTokens()) {
                String s = tokenizer.nextToken();
                systemLogger.log(Level.FINEST, MODULE, sMethod, "Token=[" + s + "]");
                String sDecoded = URLDecoder.decode(s, "UTF-8");
                // RH, 20140307, so
                //            if (sDecoded.equals("RelayState=[RelayState]"))
                //               ///////////////// empty RelayState should not be have been send, so we'll ignore it
                //               ; // 20091118, Bauke: ignore "empty" RelayState (came from logout_info.html)
                //            else if (!s.startsWith("Signature=") && !s.startsWith("consent=")) {
                //               sData += s + "&";
                //            }
                // RH, 20140307, eo
                // RH, 20140307, sn
                if (sDecoded.equals("RelayState=[RelayState]"))
                    continue; // 20091118, Bauke: ignore "empty" RelayState (came from logout_info.html)
                              // keep this for backward compatibility

                // SAMLRequest=value&RelayState=value&SigAlg=value
                // SAMLResponse=value&RelayState=value&SigAlg=value
                if (s.startsWith("SAMLRequest=")) {
                    verifyData[0] = s;
                } else if (s.startsWith("SAMLResponse=")) {
                    verifyData[0] = s;
                } else if (s.startsWith("RelayState=")) {
                    verifyData[1] = s;
                } else if (s.startsWith("SigAlg=")) {
                    verifyData[2] = s;
                    String sigAlgDecoded = URLDecoder.decode(s, "UTF-8");
                    if ((key instanceof RSAPublicKey)
                            && (sigAlgDecoded.contains(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256))) {
                        signingAlgo = "SHA256withRSA"; // Allow for SHA256
                    }

                }
                // RH, 20140307, en
            }

            //         sData = sData.substring(0, sData.length() - 1); // Delete the last '&'
            sData = verifyData[0] + "&" + (verifyData[1] != null ? verifyData[1] + "&" : "") + verifyData[2];
            systemLogger.log(Level.FINE, MODULE, sMethod, "Check [" + sData + "]");

            java.security.Signature signature = java.security.Signature.getInstance(signingAlgo);
            // RM_50_01
            signature.initVerify(key);
            //         byte[] bData = sData.getBytes();   // RH, 20140307, o
            byte[] bData = sData.getBytes("UTF-8"); // RH, 20140307, o
            signature.update(bData);

            String sSig = httpRequest.getParameter("Signature");
            if (sSig == null)
                systemLogger.log(Level.SEVERE, MODULE, sMethod, "Signature NOT PRESENT");
            byte[] bSig = Base64.decode(sSig);
            return signature.verify(bSig);
        } catch (Exception e) {
            throw new MessageDecodingException("Unable to verify  signature", e);
        }
    }

    // For the new opensaml20 library
    /**
     * Check signature.
     * 
     * @param ssObject
     *            the SAML object to be checked
     * @param publicKey
     *            the public key
     * @return true, if successful
     * @throws ASelectException
     */
    public static boolean checkSignature(SignableSAMLObject ssObject, PublicKey publicKey) throws ASelectException {
        String sMethod = "checkSignature";

        ASelectSystemLogger _systemLogger = ASelectSystemLogger.getHandle();
        Signature sig = ssObject.getSignature();

        _systemLogger.log(Level.INFO, MODULE, sMethod, "publicKey=" + publicKey + " signature=" + sig);
        if (sig == null) {
            _systemLogger.log(Level.WARNING, MODULE, sMethod, "No signature found in SAML object");
            return false;
        }

        SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator();
        try {
            profileValidator.validate(sig);
        } catch (ValidationException e) {
            // Indicates signature did not conform to SAML Signature profile
            _systemLogger.log(Level.WARNING, MODULE, sMethod,
                    "Cannot validate signature, signature did not conform to SAML Signature profile", e);
            return false;
        }

        BasicCredential credential = new BasicCredential();
        credential.setPublicKey(publicKey);

        SignatureValidator sigValidator = new SignatureValidator(credential);
        try {
            sigValidator.validate(sig);
        } catch (ValidationException e) {
            _systemLogger.log(Level.WARNING, MODULE, sMethod,
                    "Cannot verify signature, signature was not cryptographically valid");
            return false;
        }
        return true;
    }

    /**
     * Check OpenSAML2 library objects for subjectLocalityAddress validity.
     * 
     * @param obj
     *            The object to be checked
     * @param refAddress
     *            Reference (ip)address to check against
     * @return valid true = valid, false otherwise
     * @throws ValidationException
     *             Thrown if an error occurs
     * @throws ASelectException
     *             the ASelect exception
     */
    public static boolean checkLocalityAddress(SAMLObject obj, String refAddress) throws ASelectException {
        // We may want to check the DNSName here
        boolean valid = false;
        String sMethod = "checkLocalityAddress";
        ASelectSystemLogger _systemLogger = ASelectSystemLogger.getHandle();
        _systemLogger.log(Level.INFO, MODULE, sMethod, "obj->" + obj + "refAddress->" + refAddress);
        // This might be implemented more elegantly
        if ((obj instanceof AuthnStatement) && (refAddress != null)) {
            if (((AuthnStatement) obj).getSubjectLocality() != null
                    && refAddress.equals(((AuthnStatement) obj).getSubjectLocality().getAddress())) {
                valid = true;
            }
            // There might be more saml2 types to implement here
        } else {
            _systemLogger.log(Level.SEVERE, MODULE, sMethod,
                    "Cannot validate the object:" + obj + " with refAddress:" + refAddress);
            throw new ASelectException(Errors.ERROR_ASELECT_INTERNAL_ERROR);
        }
        _systemLogger.log(Level.INFO, MODULE, sMethod, "checkLocalityAddress evaluates to: " + valid);
        return valid;
    }

    /**
     * Check OpenSAML2 library objects for timeRestrictions NotBefore and NotOnOrAfter comparing with now.
     * 
     * @param obj
     *            The object to be checked
     * @return valid true = valid, false otherwise (invalid or undetermined)
     * @throws ValidationException
     *             Thrown if an error occurs
     * @throws ASelectException
     *             the a select exception
     */
    public static boolean checkValidityInterval(SAMLObject obj) throws ASelectException {
        return checkValidityInterval(obj, new DateTime());
    }

    /**
     * Check OpenSAML2 library objects for timeRestrictions NotBefore and NotOnOrAfter.
     * 
     * @param obj
     *            The object to be checked
     * @param refInstant
     *            Reference moment in time
     * @return valid true = valid, false otherwise (invalid or undetermined)
     * @throws ValidationException
     *             Thrown if an error occurs
     * @throws ASelectException
     *             the a select exception
     */
    public static boolean checkValidityInterval(SAMLObject obj, DateTime refInstant) throws ASelectException {
        boolean valid = true;
        String sMethod = "checkValidityInterval";
        ASelectSystemLogger _systemLogger = ASelectSystemLogger.getHandle();
        _systemLogger.log(Level.INFO, MODULE, sMethod, "refInstant=" + refInstant);

        // We could do it with some sort of command pattern, for now we do it the "hard" way
        // We would have been happy with some common ancestor that implements Conditions or so ;-)
        DateTime nbf = null;
        DateTime nooa = null;
        if (obj instanceof Assertion) {
            if (((Assertion) obj).getConditions() != null
                    && ((Assertion) obj).getConditions().getNotBefore() != null)
                nbf = ((Assertion) obj).getConditions().getNotBefore();
            if (((Assertion) obj).getConditions() != null
                    && ((Assertion) obj).getConditions().getNotOnOrAfter() != null)
                nooa = ((Assertion) obj).getConditions().getNotOnOrAfter();
        } else if (obj instanceof AuthnRequest) {
            if (((AuthnRequest) obj).getConditions() != null
                    && ((AuthnRequest) obj).getConditions().getNotBefore() != null)
                nbf = ((AuthnRequest) obj).getConditions().getNotBefore();
            if (((AuthnRequest) obj).getConditions() != null
                    && ((AuthnRequest) obj).getConditions().getNotOnOrAfter() != null)
                nooa = ((AuthnRequest) obj).getConditions().getNotOnOrAfter();
        } else if (obj instanceof LogoutRequest) {
            nooa = ((LogoutRequest) obj).getNotOnOrAfter();
        } else if (obj instanceof SubjectConfirmationData) {
            nooa = ((SubjectConfirmationData) obj).getNotOnOrAfter();
            nbf = ((SubjectConfirmationData) obj).getNotBefore();
        }
        // Other saml2 types would go here

        // Refer to saml2-core (2.5.1.2 Attributes NotBefore and NotOnOrAfter)
        if (nbf != null && refInstant.isBefore(nbf)) {
            valid = false;
        }
        if (nooa != null && !refInstant.isBefore(nooa)) {
            valid = false;
        }
        _systemLogger.log(Level.INFO, MODULE, sMethod, "checkValidityInterval evaluates to: " + valid);
        return valid;
    }

    /**
     * Set OpenSAML2 library Conditions object for timeRestrictions NotBefore and NotOnOrAfter.
     * 
     * @param obj
     *            The object to which conditions are to be added
     * @param refInstant
     *            Reference moment in time
     * @param maxNotBefore
     *            the max not before
     * @param maxNotOnOrAfter
     *            the max not on or after
     * @return valid Object with conditions (if not all timeRestrictions were null) otherwise return same object
     *         unmodified
     * @throws ValidationException
     *             Thrown if an error occurs while placing conditions
     * @throws ASelectException
     */
    public static SAMLObject setValidityInterval(SAMLObject obj, DateTime refInstant, Long maxNotBefore,
            Long maxNotOnOrAfter) throws ASelectException {
        String sMethod = "setValidityInterval";
        ASelectSystemLogger _systemLogger = ASelectSystemLogger.getHandle();
        _systemLogger.log(Level.INFO, MODULE, sMethod, "obj->" + obj + ", refInstant->" + refInstant
                + ", maxNotBefore->" + maxNotBefore + ", maxNotOnOrAfter->" + maxNotOnOrAfter);

        // Still think this is a bit clumsy, maybe implement some sort of
        // (command) pattern here or use generics
        if (obj instanceof Assertion) {
            Conditions conditions = ((Assertion) obj).getConditions();
            if (maxNotBefore != null || maxNotOnOrAfter != null) {
                XMLObjectBuilderFactory oBuilderFactory = org.opensaml.xml.Configuration.getBuilderFactory();
                SAMLObjectBuilder<Conditions> conditionsBuilder = (SAMLObjectBuilder<Conditions>) oBuilderFactory
                        .getBuilder(Conditions.DEFAULT_ELEMENT_NAME);

                if (maxNotBefore != null) {
                    conditions = (conditions == null) ? conditionsBuilder.buildObject() : conditions;
                    conditions.setNotBefore(refInstant.minus(maxNotBefore.longValue()));
                }
                if (maxNotOnOrAfter != null) {
                    conditions = (conditions == null) ? conditionsBuilder.buildObject() : conditions;
                    conditions.setNotOnOrAfter(refInstant.plus(maxNotOnOrAfter.longValue()));
                }
            }
            if (conditions != null) {
                ((Assertion) obj).setConditions(conditions);
                _systemLogger.log(Level.INFO, MODULE, sMethod, "Conditions set on Assertion->" + obj);
            }
        } else // not instanceof Assertion
        if (obj instanceof AuthnRequest) {
            Conditions conditions = ((AuthnRequest) obj).getConditions();
            if (maxNotBefore != null || maxNotOnOrAfter != null) {
                XMLObjectBuilderFactory oBuilderFactory = org.opensaml.xml.Configuration.getBuilderFactory();
                SAMLObjectBuilder<Conditions> conditionsBuilder = (SAMLObjectBuilder<Conditions>) oBuilderFactory
                        .getBuilder(Conditions.DEFAULT_ELEMENT_NAME);

                if (maxNotBefore != null) {
                    conditions = (conditions == null) ? conditionsBuilder.buildObject() : conditions;
                    conditions.setNotBefore(refInstant.minus(maxNotBefore.longValue()));
                }
                if (maxNotOnOrAfter != null) {
                    conditions = (conditions == null) ? conditionsBuilder.buildObject() : conditions;
                    conditions.setNotOnOrAfter(refInstant.plus(maxNotOnOrAfter.longValue()));
                }
            }
            if (conditions != null) {
                ((AuthnRequest) obj).setConditions(conditions);
                _systemLogger.log(Level.INFO, MODULE, sMethod, "Conditions set on AuthnRequest->" + obj);
            }
        } else // not instanceof AuthnRequest
        if (obj instanceof SubjectConfirmationData) {
            if (maxNotBefore != null) {
                ((SubjectConfirmationData) obj).setNotBefore(refInstant.minus(maxNotBefore.longValue()));
            }
            if (maxNotOnOrAfter != null) {
                ((SubjectConfirmationData) obj).setNotOnOrAfter(refInstant.plus(maxNotOnOrAfter.longValue()));
            }
        } else // not instanceof SubjectConfirmationData
        if (obj instanceof LogoutRequest) {
            if (maxNotOnOrAfter != null) {
                ((LogoutRequest) obj).setNotOnOrAfter(refInstant.plus(maxNotOnOrAfter.longValue()));
            }
        } // not instanceof LogoutRequest

        return obj;
    }

    /**
     * Set OpenSAML2 library Conditions object for timeRestrictions NotBefore and NotOnOrAfter.
     * 
     * @param obj
     *            The object to which restriction are to be added
     * @param restriction
     *            AudienceRestriction to add to Condition of this object (create Condition if not exists
     * @return valid Object with restrictions/conditions (if not restriction == null)
     * @throws ValidationException
     *             Thrown if an error occurs while placing conditions
     * @throws ASelectException
     *             the a select exception
     */
    public static SAMLObject setAudienceRestrictions(SAMLObject obj, AudienceRestriction restriction)
            throws ASelectException {

        String sMethod = "setAudienceRestrictions";
        ASelectSystemLogger _systemLogger = ASelectSystemLogger.getHandle();
        _systemLogger.log(Level.INFO, MODULE, sMethod, "obj->" + obj + ", restriction->" + restriction);
        // Still think this is a bit clumsy, maybe implement some sort of
        // (command) pattern here or use generics
        if (obj instanceof Assertion) {
            Conditions conditions = null;
            if (restriction != null) {
                if (((Assertion) obj).getConditions() == null) {
                    XMLObjectBuilderFactory oBuilderFactory = org.opensaml.xml.Configuration.getBuilderFactory();
                    SAMLObjectBuilder<Conditions> conditionsBuilder = (SAMLObjectBuilder<Conditions>) oBuilderFactory
                            .getBuilder(Conditions.DEFAULT_ELEMENT_NAME);
                    ((Assertion) obj).setConditions(conditions);
                    _systemLogger.log(Level.INFO, MODULE, sMethod, "Conditions set on Assertion->" + obj);
                    ((Assertion) obj).setConditions(conditionsBuilder.buildObject());
                }
                ((Assertion) obj).getConditions().getAudienceRestrictions().add(restriction);
            }
        }
        // Other SAMLObjects would go here
        return obj;
    }

    // For the new opensaml20 library
    /**
     * Sign OpenSAML2 library objects (including both SAML versions 1 and 2).
     * 
     * @param obj
     *            The object to be signed
     * @return obj The signed object
     * @throws ValidationException
     *             Thrown if an error occurs while signing
     * @throws ASelectException
     *             the a select exception
     */
    public static SignableSAMLObject signSamlObject(SignableSAMLObject obj) throws ASelectException {
        return signSamlObject(obj, "sha1"); // default algorithm
    }

    /*
     * @param sAlgo
     *            The algorithm to use [ "sha256" | "sha1" ] defaults to "sha1"
     */
    public static SignableSAMLObject signSamlObject(SignableSAMLObject obj, String sAlgo) throws ASelectException {
        return signSamlObject(obj, sAlgo, false, false);
    }

    /*
     * @param    addKeyName         
     *            Add the (default) keyname in a KeyInfo element
     * @param    addCertificate         
     *            Add the (default) certificate in a KeyInfo element
     */
    public static SignableSAMLObject signSamlObject(SignableSAMLObject obj, String sAlgo, boolean addKeyName,
            boolean addCertificate) throws ASelectException {
        String sMethod = "sign(SignableSAMLObject obj)";
        ASelectSystemLogger _systemLogger = ASelectSystemLogger.getHandle();
        boolean useSha256 = "sha256".equals(sAlgo);

        _systemLogger.log(Level.INFO, MODULE, sMethod, "obj->" + obj);
        if (!obj.isSigned()) {
            ASelectConfigManager _oASelectConfigManager = ASelectConfigManager.getHandle();
            PrivateKey privKey = _oASelectConfigManager.getDefaultPrivateKey();
            Signature signature = new SignatureBuilder().buildObject();
            String signingAlgo;
            if ("RSA".equalsIgnoreCase(privKey.getAlgorithm())) {
                signingAlgo = (useSha256) ? SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256
                        : SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1;
            } else {
                signingAlgo = SignatureConstants.ALGO_ID_SIGNATURE_ECDSA_SHA1;
            }
            _systemLogger.log(Level.INFO, MODULE, sMethod,
                    "privKey algorithm=" + privKey.getAlgorithm() + " use signing algorithm: " + signingAlgo);

            BasicCredential credential = new BasicCredential();
            credential.setPrivateKey(privKey);
            signature.setSigningCredential(credential);
            signature.setSignatureAlgorithm(signingAlgo);
            signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);

            // add keyinfo
            if (addKeyName || addCertificate) {
                _systemLogger.log(Level.INFO, MODULE, sMethod, "Adding keyinfo");

                // Tried KeyInfoGenerator but.generate(credential)  always return null, so 
                // build keyinfo manually
                XMLObjectBuilderFactory builderFactory = Configuration.getBuilderFactory();
                KeyInfoBuilder keyInfoBuilder = (KeyInfoBuilder) builderFactory
                        .getBuilder(KeyInfo.DEFAULT_ELEMENT_NAME);
                KeyInfo keyinfo = (KeyInfo) keyInfoBuilder.buildObject(KeyInfo.DEFAULT_ELEMENT_NAME);
                java.security.cert.X509Certificate x509Certificate = _oASelectConfigManager.getDefaultCertificate();
                if (addCertificate) {
                    try {
                        KeyInfoHelper.addCertificate(keyinfo, x509Certificate);
                    } catch (CertificateEncodingException e) {
                        _systemLogger.log(Level.SEVERE, MODULE, sMethod, "Problem adding certificate to keyinfo");
                        throw new ASelectException(e.getMessage());
                    }
                }
                if (addKeyName) {
                    KeyInfoHelper.addKeyName(keyinfo, _oASelectConfigManager.getDefaultCertId());
                }
                signature.setKeyInfo(keyinfo);
                _systemLogger.log(Level.INFO, MODULE, sMethod, "Added keyinfo");
            } else {
                _systemLogger.log(Level.INFO, MODULE, sMethod, "No keyinfo added");
            }

            obj.setSignature(signature);

            // 20100315, Bauke: Set Sha256, call after setSignature, remove all ContentReferences,
            // including the default and add a new one for Sha256 (eHerkenning)
            if (useSha256) {
                SAMLObjectContentReference contentReference = new SAMLObjectContentReference(obj);
                contentReference.setDigestAlgorithm(EncryptionConstants.ALGO_ID_DIGEST_SHA256);
                signature.getContentReferences().clear(); // must be done after setSignature() (it adds a default to the list)
                signature.getContentReferences().add(contentReference);
            }

            try {
                org.opensaml.xml.Configuration.getMarshallerFactory().getMarshaller(obj).marshall(obj);
            } catch (MarshallingException e) {
                _systemLogger.log(Level.SEVERE, MODULE, sMethod, "Cannot marshall object for signature", e);
                throw new ASelectException(Errors.ERROR_ASELECT_INTERNAL_ERROR, e);
            }
            try {
                Signer.signObject(signature);
            } catch (SignatureException e) {
                _systemLogger.log(Level.SEVERE, MODULE, sMethod, "Cannot sign the object", e);
                throw new ASelectException(Errors.ERROR_ASELECT_INTERNAL_ERROR, e);
            }
        } else
            _systemLogger.log(Level.INFO, MODULE, sMethod, "Object already signed!");

        return obj;
    }

    /**
     * Build Logout Request <br>
     * .
     * 
     * @param sServiceProviderUrl
     *            String with SP .
     * @param issuerUrl
     *            String with Issuer .
     * @param reason
     *            String with logout reason.
     * @param sTgT
     *            the s tg t
     * @param sNameID
     *            the s name id
     * @param  List<String>sessionindexes
     *             optional list of sessionindexes to kill
     * @return the logout request
     * @throws ASelectException
     *             If building logout request fails.
     */
    //   public static LogoutRequest buildLogoutRequest(String sServiceProviderUrl, String sTgT, String sNameID,
    //         String issuerUrl, String reason)
    //   throws ASelectException   {   // for backward compatibility
    //      return buildLogoutRequest(sServiceProviderUrl, sTgT, sNameID,
    //            issuerUrl, reason, null);
    //   }

    public static LogoutRequest buildLogoutRequest(String sServiceProviderUrl, String sTgT, String sNameID,
            String issuerUrl, String reason, List<String> sessionindexes) throws ASelectException {
        return buildLogoutRequest(sServiceProviderUrl, sTgT, sNameID, issuerUrl, reason, sessionindexes, null);
    }

    @SuppressWarnings("unchecked")
    public static LogoutRequest buildLogoutRequest(String sServiceProviderUrl, String sTgT, String sNameID,
            String issuerUrl, String reason, List<String> sessionindexes, DateTime issueInstant)
            throws ASelectException {
        String sMethod = "buildLogoutRequest";
        ASelectSystemLogger systemLogger = ASelectSystemLogger.getHandle();
        systemLogger.log(Level.INFO, MODULE, sMethod, "provider=" + sServiceProviderUrl);

        XMLObjectBuilderFactory builderFactory = org.opensaml.xml.Configuration.getBuilderFactory();
        SAMLObjectBuilder<LogoutRequest> logoutRequestBuilder = (SAMLObjectBuilder<LogoutRequest>) builderFactory
                .getBuilder(LogoutRequest.DEFAULT_ELEMENT_NAME);
        LogoutRequest logoutRequest = logoutRequestBuilder.buildObject();
        // verplichte velden
        logoutRequest.setID((sTgT != null) ? "_" + sTgT : SamlTools.generateIdentifier(systemLogger, MODULE));
        logoutRequest.setVersion(SAMLVersion.VERSION_20);

        if (issueInstant == null)
            issueInstant = new DateTime(); // RH, 20120307, n

        //      logoutRequest.setIssueInstant(new DateTime());   // RH, 20120307, o
        logoutRequest.setIssueInstant(issueInstant); // RH, 20120307, n

        // een van de volgende 3 is verplicht baseId, encryptedId, nameId
        SAMLObjectBuilder<NameID> nameIdBuilder = (SAMLObjectBuilder<NameID>) builderFactory
                .getBuilder(NameID.DEFAULT_ELEMENT_NAME);
        NameID nameId = nameIdBuilder.buildObject();
        nameId.setFormat(NameIDType.TRANSIENT); // RH, 20140801, n. We use transient id's for nameid
        nameId.setValue(sNameID);
        logoutRequest.setNameID(nameId);

        // optionele velden
        logoutRequest.setReason(reason);
        logoutRequest.setDestination(sServiceProviderUrl);

        // RH, 20120130, sn
        //   add optional SessionIndexes (Surf requires at least one)
        if (sessionindexes != null) {
            SAMLObjectBuilder<SessionIndex> sessionindexBuilder = (SAMLObjectBuilder<SessionIndex>) builderFactory
                    .getBuilder(SessionIndex.DEFAULT_ELEMENT_NAME);
            for (String sSession : sessionindexes) {
                SessionIndex sessionindex = sessionindexBuilder.buildObject();
                sessionindex.setSessionIndex(sSession);
                logoutRequest.getSessionIndexes().add(sessionindex);
            }
        }
        // RH, 20120130, en

        SAMLObjectBuilder<Issuer> issuerBuilder = (SAMLObjectBuilder<Issuer>) builderFactory
                .getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
        Issuer issuer = issuerBuilder.buildObject();
        //      issuer.setFormat(Issuer.ENTITY);    // saml specs say, MUST  be omitted or "entity" for Issuer
        issuer.setValue(issuerUrl);
        logoutRequest.setIssuer(issuer);

        return logoutRequest;
    }

    /**
     * Build Logout Response. <br>
     * 
     * @param issuer
     *            String with issuer.
     * @param statusCodeValue
     *            String with ???.
     * @param inResponseTo
     *            String with ???.
     * @return the logout response
     * @throws ASelectException
     *             If building logout response fails.
     */
    @SuppressWarnings("unchecked")
    public static LogoutResponse buildLogoutResponse(String issuer, String statusCodeValue, String inResponseTo)
            throws ASelectException {
        String sMethod = "buildLogoutResponse";
        ASelectSystemLogger systemLogger = ASelectSystemLogger.getHandle();

        XMLObjectBuilderFactory builderFactory = org.opensaml.xml.Configuration.getBuilderFactory();
        SAMLObjectBuilder<LogoutResponse> logoutResponseBuilder = (SAMLObjectBuilder<LogoutResponse>) builderFactory
                .getBuilder(LogoutResponse.DEFAULT_ELEMENT_NAME);
        LogoutResponse logoutResponse = logoutResponseBuilder.buildObject();

        // Mandatory fields:
        String random;
        try {
            random = SamlTools.generateIdentifier(systemLogger, MODULE);
        } catch (ASelectException e) {
            systemLogger.log(Level.WARNING, MODULE, sMethod, e.getMessage());
            // if generator failed we can use this
            random = "random" + Math.random();
        }
        logoutResponse.setID(random);
        logoutResponse.setVersion(SAMLVersion.VERSION_20);
        logoutResponse.setIssueInstant(new DateTime());

        SAMLObjectBuilder<Status> statusBuilder = (SAMLObjectBuilder<Status>) builderFactory
                .getBuilder(Status.DEFAULT_ELEMENT_NAME);
        Status status = statusBuilder.buildObject();
        SAMLObjectBuilder<StatusCode> statusCodeBuilder = (SAMLObjectBuilder<StatusCode>) builderFactory
                .getBuilder(StatusCode.DEFAULT_ELEMENT_NAME);
        StatusCode statusCode = statusCodeBuilder.buildObject();
        statusCode.setValue(statusCodeValue);
        status.setStatusCode(statusCode);
        logoutResponse.setStatus(status);

        SAMLObjectBuilder<Issuer> issuerBuilder = (SAMLObjectBuilder<Issuer>) builderFactory
                .getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
        Issuer issuerObject = issuerBuilder.buildObject();
        issuerObject.setValue(issuer);
        logoutResponse.setIssuer(issuerObject);
        logoutResponse.setInResponseTo(inResponseTo);

        MarshallerFactory factory = org.opensaml.xml.Configuration.getMarshallerFactory();
        Marshaller marshaller = factory.getMarshaller(logoutResponse);
        try {
            Node node = marshaller.marshall(logoutResponse);
            String msg = XMLHelper.prettyPrintXML(node);
            systemLogger.log(Level.INFO, MODULE, sMethod, "Built message:\n" + msg);
        } catch (MarshallingException e) {
            systemLogger.log(Level.WARNING, MODULE, sMethod, "Exception marhalling message: " + e.getMessage());
            throw new ASelectException(Errors.ERROR_ASELECT_INTERNAL_ERROR, e);
        }
        return logoutResponse;
    }

    /**
     * Gets the node.
     * 
     * @param node
     *            the node
     * @param sSearch
     *            the s search
     * @return the node
     */
    public static Node getNode(Node node, String sSearch) {
        Node nResult = null;
        NodeList nodeList = node.getChildNodes();
        for (int i = 0; i < nodeList.getLength() && nResult == null; i++) {
            if (sSearch.equals(nodeList.item(i).getLocalName()))
                nResult = nodeList.item(i);
            else
                nResult = getNode(nodeList.item(i), sSearch);
        }
        return nResult;
    }

    /**
     * Helper method that marshalls the given message.
     * 
     * @param message
     *            message the marshall and serialize
     * @return marshalled message
     * @throws MessageEncodingException
     *             thrown if the give message can not be marshalled into its DOM representation
     */
    public static Element marshallMessage(XMLObject message) throws MessageEncodingException {
        String sMethod = "marshallMessage";
        ASelectSystemLogger systemLogger = ASelectSystemLogger.getHandle();

        try {
            Marshaller marshaller = org.opensaml.xml.Configuration.getMarshallerFactory().getMarshaller(message);
            if (marshaller == null) {
                systemLogger.log(Level.INFO, MODULE, sMethod,
                        "Unable to marshall message, no marshaller registered for message object: "
                                + message.getElementQName());
            }
            Element messageElem = marshaller.marshall(message);
            // systemLogger.log(Level.INFO, MODULE, sMethod,
            // "Marshalled message into DOM:\n"+XMLHelper.nodeToString(messageElem));

            return messageElem;
        } catch (MarshallingException e) {
            throw new MessageEncodingException("Encountered error marshalling message into its DOM representation",
                    e);
        }
    }

    /**
     * Unmarshall element.
     * 
     * @param element
     *            the element
     * @return the xML object
     * @throws MessageEncodingException
     *             the message encoding exception
     */
    public static XMLObject unmarshallElement(Element element) throws MessageEncodingException {
        String sMethod = "unmarshallMessage";
        ASelectSystemLogger systemLogger = ASelectSystemLogger.getHandle();

        try {
            Unmarshaller unmarshaller = org.opensaml.xml.Configuration.getUnmarshallerFactory()
                    .getUnmarshaller(element);
            if (unmarshaller == null) {
                systemLogger.log(Level.INFO, MODULE, sMethod,
                        "Unable to unmarshall element, no unmarshaller registered for element object: " + element);
            }
            XMLObject xmlObject = unmarshaller.unmarshall(element);
            systemLogger.log(Level.INFO, MODULE, sMethod, "Unmarshalled element to: " + xmlObject.getClass());

            return xmlObject;
        } catch (UnmarshallingException e) {
            throw new MessageEncodingException(
                    "Encountered error unmarshalling element into its object representation", e);
        }
    }

    // Wrapper class for transition from our "old" "trunk" jars to
    // release version of opensaml/2.1.0, openws/1.1.0, xmltooling/1.0.1
    // Catches old org.opensaml.xml.signature.KeyInfoHelper.getCertificate()
    /**
     * Gets the certificate.
     * 
     * @param cert
     *            the cert
     * @return the certificate
     * @throws CertificateException
     *             the certificate exception
     */
    public static java.security.cert.X509Certificate getCertificate(X509Certificate cert)
            throws CertificateException {
        return org.opensaml.xml.security.keyinfo.KeyInfoHelper.getCertificate(cert);
    }

    // Wrapper class for transition from our "old" "trunk" jars to
    // release version of opensaml/2.1.0, openws/1.1.0, xmltooling/1.0.1
    // Catches old HttpServletResponseAdapter(HttpServletResponse response) constructor
    /**
     * Creates the http servlet response adapter.
     * 
     * @param response
     *            the response
     * @param remoteURL
     *            the remote url
     * @return the http servlet response adapter
     */
    public static HttpServletResponseAdapter createHttpServletResponseAdapter(HttpServletResponse response,
            String remoteURL) {
        return new HttpServletResponseAdapter(response,
                remoteURL == null ? false : remoteURL.toLowerCase().startsWith("https"));
    }

    /**
     * Set saml20 appropriate headers and send the HTTP SOAP response and close the stream.
     * 
     * @param servletResponse
     *            , the servletresponse
     * @param envelope
     *            , the (soapenvelope) string to send
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    public static void sendSOAPResponse(HttpServletRequest servletRequest, HttpServletResponse servletResponse,
            String envelope) throws IOException {
        PrintWriter pwOut = Utils.prepareForHtmlOutput(servletRequest, servletResponse);
        pwOut.write(envelope);
        pwOut.write("\r\n\r\n\r\n"); // Backward compatibility
        pwOut.close();
    }

    //
    // Create a new HashMap based on an htSource
    // If include is true only include the attributes mentioned in arrAttr
    // Else take htSource, but exclude the attributes in arrAttr from the result.
    //
    /**
     * Extract from hashtable.
     * 
     * @param arrAttr
     *            the arr attr
     * @param htSource
     *            the ht source
     * @param include
     *            the include
     * @return the hash map
     */
    public static HashMap extractFromHashtable(String[] arrAttr, HashMap<String, Object> htSource,
            boolean include) {
        Object oValue;
        HashMap<String, Object> htResult = new HashMap<String, Object>();

        if (!include)
            htResult.putAll(htSource);
        for (int i = 0; i < arrAttr.length; i++) {
            oValue = htSource.get(arrAttr[i]);
            if (include && oValue != null)
                htResult.put(arrAttr[i], oValue);
            if (!include)
                htResult.remove(arrAttr[i]);
        }
        return htResult;
    }

    /**
     * Map SAML statuscodes to aselect errorcodes.
     * 
     * @param samlstatuscode
     *            the saml statuscode ( urn:oasis:names:tc:SAML:2.0:status:xxxx )
     * 
     * @return the aselect errorcode
     */
    public static String mapStatus(String s) {
        String sMethod = "mapStatus";
        ASelectSystemLogger systemLogger = ASelectSystemLogger.getHandle();
        systemLogger.log(Level.FINER, MODULE, sMethod, "Mapping StatusCode: " + s);

        if (StatusCode.SUCCESS_URI.equals(s)) {
            return Errors.ERROR_ASELECT_SUCCESS;
        } else if (StatusCode.AUTHN_FAILED_URI.equals(s)) {
            return Errors.ERROR_ASELECT_AUTHSP_COULD_NOT_AUTHENTICATE_USER;
        } else if (StatusCode.NO_AUTHN_CONTEXT_URI.equals(s)) {
            return Errors.ERROR_ASELECT_AUTHSP_NO_AUTHN_CONTEXT;
        } else if (StatusCode.PARTIAL_LOGOUT_URI.equals(s)) {
            return Errors.ERROR_ASELECT_AUTHSP_PARTIAL_LOGOUT;
        } else if (StatusCode.REQUEST_DENIED_URI.equals(s)) {
            return Errors.ERROR_ASELECT_AUTHSP_REQUEST_DENIED;
        } else
            return (String) null;

    }

}