be.fedict.eid.idp.common.saml2.Saml2Util.java Source code

Java tutorial

Introduction

Here is the source code for be.fedict.eid.idp.common.saml2.Saml2Util.java

Source

/*
 * eID Identity Provider Project.
 * Copyright (C) 2010 FedICT.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version
 * 3.0 as published by the Free Software Foundation.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, see
 * http://www.gnu.org/licenses/.
 */

package be.fedict.eid.idp.common.saml2;

import java.io.IOException;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.crypto.SecretKey;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.SignedInfo;
import javax.xml.crypto.dsig.XMLSignContext;
import javax.xml.crypto.dsig.XMLSignatureException;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xml.security.utils.Constants;
import org.apache.xpath.XPathAPI;
import org.joda.time.DateTime;
import org.joda.time.chrono.ISOChronology;
import org.opensaml.Configuration;
import org.opensaml.DefaultBootstrap;
import org.opensaml.common.SAMLVersion;
import org.opensaml.common.SignableSAMLObject;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml2.core.Assertion;
import org.opensaml.saml2.core.Attribute;
import org.opensaml.saml2.core.AttributeStatement;
import org.opensaml.saml2.core.AttributeValue;
import org.opensaml.saml2.core.Audience;
import org.opensaml.saml2.core.AudienceRestriction;
import org.opensaml.saml2.core.AuthnContext;
import org.opensaml.saml2.core.AuthnContextClassRef;
import org.opensaml.saml2.core.AuthnStatement;
import org.opensaml.saml2.core.Conditions;
import org.opensaml.saml2.core.EncryptedAttribute;
import org.opensaml.saml2.core.Issuer;
import org.opensaml.saml2.core.NameID;
import org.opensaml.saml2.core.NameIDType;
import org.opensaml.saml2.core.OneTimeUse;
import org.opensaml.saml2.core.Response;
import org.opensaml.saml2.core.Status;
import org.opensaml.saml2.core.StatusCode;
import org.opensaml.saml2.core.Subject;
import org.opensaml.saml2.core.SubjectConfirmation;
import org.opensaml.saml2.core.SubjectConfirmationData;
import org.opensaml.saml2.encryption.Decrypter;
import org.opensaml.saml2.encryption.EncryptedElementTypeEncryptedKeyResolver;
import org.opensaml.saml2.encryption.Encrypter;
import org.opensaml.saml2.metadata.AssertionConsumerService;
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.SingleSignOnService;
import org.opensaml.security.SAMLSignatureProfileValidator;
import org.opensaml.ws.wstrust.KeyType;
import org.opensaml.ws.wstrust.RequestSecurityTokenResponse;
import org.opensaml.ws.wstrust.RequestSecurityTokenResponseCollection;
import org.opensaml.ws.wstrust.RequestType;
import org.opensaml.ws.wstrust.RequestedSecurityToken;
import org.opensaml.ws.wstrust.TokenType;
import org.opensaml.ws.wstrust.impl.KeyTypeBuilder;
import org.opensaml.ws.wstrust.impl.KeyTypeMarshaller;
import org.opensaml.ws.wstrust.impl.KeyTypeUnmarshaller;
import org.opensaml.ws.wstrust.impl.RequestSecurityTokenResponseBuilder;
import org.opensaml.ws.wstrust.impl.RequestSecurityTokenResponseCollectionBuilder;
import org.opensaml.ws.wstrust.impl.RequestSecurityTokenResponseCollectionMarshaller;
import org.opensaml.ws.wstrust.impl.RequestSecurityTokenResponseCollectionUnmarshaller;
import org.opensaml.ws.wstrust.impl.RequestSecurityTokenResponseMarshaller;
import org.opensaml.ws.wstrust.impl.RequestSecurityTokenResponseUnmarshaller;
import org.opensaml.ws.wstrust.impl.RequestTypeBuilder;
import org.opensaml.ws.wstrust.impl.RequestTypeMarshaller;
import org.opensaml.ws.wstrust.impl.RequestTypeUnmarshaller;
import org.opensaml.ws.wstrust.impl.RequestedSecurityTokenBuilder;
import org.opensaml.ws.wstrust.impl.RequestedSecurityTokenMarshaller;
import org.opensaml.ws.wstrust.impl.RequestedSecurityTokenUnmarshaller;
import org.opensaml.ws.wstrust.impl.TokenTypeBuilder;
import org.opensaml.ws.wstrust.impl.TokenTypeMarshaller;
import org.opensaml.ws.wstrust.impl.TokenTypeUnmarshaller;
import org.opensaml.xml.ConfigurationException;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.XMLObjectBuilder;
import org.opensaml.xml.XMLObjectBuilderFactory;
import org.opensaml.xml.encryption.DecryptionException;
import org.opensaml.xml.encryption.EncryptionConstants;
import org.opensaml.xml.encryption.EncryptionException;
import org.opensaml.xml.encryption.EncryptionParameters;
import org.opensaml.xml.encryption.KeyEncryptionParameters;
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.schema.XSBase64Binary;
import org.opensaml.xml.schema.XSDateTime;
import org.opensaml.xml.schema.XSInteger;
import org.opensaml.xml.schema.XSString;
import org.opensaml.xml.security.credential.BasicCredential;
import org.opensaml.xml.security.credential.UsageType;
import org.opensaml.xml.security.keyinfo.KeyInfoCredentialResolver;
import org.opensaml.xml.security.keyinfo.KeyInfoGeneratorFactory;
import org.opensaml.xml.security.keyinfo.KeyInfoHelper;
import org.opensaml.xml.security.keyinfo.StaticKeyInfoCredentialResolver;
import org.opensaml.xml.security.keyinfo.StaticKeyInfoGenerator;
import org.opensaml.xml.security.x509.BasicX509Credential;
import org.opensaml.xml.security.x509.X509KeyInfoGeneratorFactory;
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.impl.SignatureBuilder;
import org.opensaml.xml.util.Base64;
import org.opensaml.xml.validation.ValidationException;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import be.fedict.eid.idp.common.SamlAuthenticationPolicy;

/**
 * Utility class for SAML v2.0
 * 
 * @author Wim Vandenhaute
 */
public abstract class Saml2Util {

    private static final Log LOG = LogFactory.getLog(Saml2Util.class);

    static {
        /*
         * Next is because Sun loves to endorse crippled versions of Xerces.
         */
        System.setProperty("javax.xml.validation.SchemaFactory:http://www.w3.org/2001/XMLSchema",
                "org.apache.xerces.jaxp.validation.XMLSchemaFactory");
        try {
            DefaultBootstrap.bootstrap();

            // register WS-Trust elements needed in WS-Federation
            Configuration.registerObjectProvider(RequestSecurityTokenResponseCollection.ELEMENT_NAME,
                    new RequestSecurityTokenResponseCollectionBuilder(),
                    new RequestSecurityTokenResponseCollectionMarshaller(),
                    new RequestSecurityTokenResponseCollectionUnmarshaller());

            Configuration.registerObjectProvider(RequestSecurityTokenResponse.ELEMENT_NAME,
                    new RequestSecurityTokenResponseBuilder(), new RequestSecurityTokenResponseMarshaller(),
                    new RequestSecurityTokenResponseUnmarshaller());

            Configuration.registerObjectProvider(TokenType.ELEMENT_NAME, new TokenTypeBuilder(),
                    new TokenTypeMarshaller(), new TokenTypeUnmarshaller());

            Configuration.registerObjectProvider(RequestType.ELEMENT_NAME, new RequestTypeBuilder(),
                    new RequestTypeMarshaller(), new RequestTypeUnmarshaller());

            Configuration.registerObjectProvider(KeyType.ELEMENT_NAME, new KeyTypeBuilder(),
                    new KeyTypeMarshaller(), new KeyTypeUnmarshaller());

            Configuration.registerObjectProvider(RequestedSecurityToken.ELEMENT_NAME,
                    new RequestedSecurityTokenBuilder(), new RequestedSecurityTokenMarshaller(),
                    new RequestedSecurityTokenUnmarshaller());

        } catch (ConfigurationException e) {
            throw new RuntimeException("could not bootstrap the OpenSAML2 library", e);
        }
    }

    /**
     * Returns SAML v2.0 Metadata {@link EntityDescriptor} with 1
     * {@link AssertionConsumerService} at specified location with specified
     * binding.
     * 
     * @param entityId
     *            entity ID (== response.issuer)
     * @param location
     *            location
     * @param binding
     *            SAML v2.0 Binding
     * @param identity
     *            optional identity, if present key descriptor will be added.
     * @return the metadata entity descriptor
     */
    public static EntityDescriptor getEntityDescriptor(String entityId, String location, String binding,
            KeyStore.PrivateKeyEntry identity) {

        // Add a descriptor for our node (the SAMLv2 Entity).
        EntityDescriptor entityDescriptor = Saml2Util.buildXMLObject(EntityDescriptor.class,
                EntityDescriptor.DEFAULT_ELEMENT_NAME);

        entityDescriptor.setEntityID(entityId);

        // signature
        if (null != identity) {
            // Add a signature to the entity descriptor.
            Signature signature = Saml2Util.buildXMLObject(Signature.class, Signature.DEFAULT_ELEMENT_NAME);
            entityDescriptor.setSignature(signature);

            signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);

            // add certificate chain as keyinfo
            signature.setKeyInfo(getKeyInfo(identity));

            BasicX509Credential signingCredential = new BasicX509Credential();
            signingCredential.setPrivateKey(identity.getPrivateKey());
            signingCredential.setEntityCertificateChain(getCertificateChain(identity));
            signature.setSigningCredential(signingCredential);

            String algorithm = identity.getPrivateKey().getAlgorithm();
            if ("RSA".equals(algorithm)) {
                signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA);
            } else {
                signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_ECDSA_SHA1);
            }
        }

        // Add a descriptor for our identity services.
        IDPSSODescriptor idpssoDescriptor = Saml2Util.buildXMLObject(IDPSSODescriptor.class,
                IDPSSODescriptor.DEFAULT_ELEMENT_NAME);
        entityDescriptor.getRoleDescriptors().add(idpssoDescriptor);

        idpssoDescriptor.setWantAuthnRequestsSigned(false);
        idpssoDescriptor.addSupportedProtocol(SAMLConstants.SAML20P_NS);

        // NameID Format
        NameIDFormat nameIDFormat = Saml2Util.buildXMLObject(NameIDFormat.class, NameIDFormat.DEFAULT_ELEMENT_NAME);
        nameIDFormat.setFormat(NameIDType.TRANSIENT);
        idpssoDescriptor.getNameIDFormats().add(nameIDFormat);

        // Key descriptor
        if (null != identity) {
            KeyDescriptor keyDescriptor = Saml2Util.buildXMLObject(KeyDescriptor.class,
                    KeyDescriptor.DEFAULT_ELEMENT_NAME);
            keyDescriptor.setKeyInfo(getKeyInfo(identity));
            keyDescriptor.setUse(UsageType.SIGNING);
            idpssoDescriptor.getKeyDescriptors().add(keyDescriptor);
        }

        // SSO services
        SingleSignOnService ssoService = Saml2Util.buildXMLObject(SingleSignOnService.class,
                SingleSignOnService.DEFAULT_ELEMENT_NAME);
        idpssoDescriptor.getSingleSignOnServices().add(ssoService);

        ssoService.setBinding(binding);
        ssoService.setLocation(location);

        return entityDescriptor;
    }

    private static KeyInfo getKeyInfo(KeyStore.PrivateKeyEntry identity) {

        List<X509Certificate> certificateChain = getCertificateChain(identity);
        KeyInfo keyInfo = Saml2Util.buildXMLObject(KeyInfo.class, KeyInfo.DEFAULT_ELEMENT_NAME);
        try {
            for (X509Certificate certificate : certificateChain) {
                KeyInfoHelper.addCertificate(keyInfo, certificate);
            }
        } catch (CertificateEncodingException e) {
            throw new RuntimeException("opensaml2 certificate encoding error: " + e.getMessage(), e);
        }
        return keyInfo;
    }

    /**
     * Return the {@link X509Certificate} chain for specified identity
     * 
     * @param identity
     *            identity to get chain from
     * @return the certificate chain.
     */
    public static List<X509Certificate> getCertificateChain(KeyStore.PrivateKeyEntry identity) {

        List<X509Certificate> certificateChain = new LinkedList<X509Certificate>();
        for (java.security.cert.Certificate certificate : identity.getCertificateChain()) {
            certificateChain.add((X509Certificate) certificate);
        }
        return certificateChain;
    }

    /**
     * Constructs a bare SAML v2.0 {@link Response} with status Success.
     * 
     * @param inResponseTo
     *            response inresponse to request.ID
     * @param targetUrl
     *            targetURL
     * @param issuerName
     *            issuer of the response
     * @return SAML v2.0 Response
     */
    public static Response getResponse(String inResponseTo, String targetUrl, String issuerName) {

        Response response = Saml2Util.buildXMLObject(Response.class, Response.DEFAULT_ELEMENT_NAME);
        DateTime issueInstant = new DateTime();
        response.setIssueInstant(issueInstant);
        response.setVersion(SAMLVersion.VERSION_20);
        response.setDestination(targetUrl);
        String samlResponseId = "saml-response-" + UUID.randomUUID().toString();
        response.setID(samlResponseId);
        response.setInResponseTo(inResponseTo);

        Issuer issuer = Saml2Util.buildXMLObject(Issuer.class, Issuer.DEFAULT_ELEMENT_NAME);
        issuer.setValue(issuerName);
        response.setIssuer(issuer);

        Status status = Saml2Util.buildXMLObject(Status.class, Status.DEFAULT_ELEMENT_NAME);
        response.setStatus(status);
        StatusCode statusCode = Saml2Util.buildXMLObject(StatusCode.class, StatusCode.DEFAULT_ELEMENT_NAME);
        status.setStatusCode(statusCode);
        statusCode.setValue(StatusCode.SUCCESS_URI);
        return response;
    }

    /**
     * Construct an unsigned SAML v2.0 Assertion
     * 
     * @param issuerName
     *            assertion issuer
     * @param inResponseTo
     *            optional inResponseTo
     * @param audienceUri
     *            audience in audience restriction
     * @param recipient
     *            recipient (SubjectConfirmationData.recipient)
     * @param tokenValidity
     *            valitity in minutes of the assertion
     * @param issueInstant
     *            time of issuance
     * @param authenticationPolicy
     *            authentication policy
     * @param userId
     *            user ID
     * @param attributes
     *            map of user's attributes
     * @param secretKey
     *            optional symmetric SecretKey used for encryption
     * @param publicKey
     *            optional RSA public key used for encryption
     * @return the unsigned SAML v2.0 assertion.
     */
    public static Assertion getAssertion(String issuerName, String inResponseTo, String audienceUri,
            String recipient, Integer tokenValidity, DateTime issueInstant,
            SamlAuthenticationPolicy authenticationPolicy, String userId,
            Map<String, be.fedict.eid.idp.common.Attribute> attributes, SecretKey secretKey, PublicKey publicKey) {

        int validity = 5;
        if (null != tokenValidity && tokenValidity > 0) {
            validity = tokenValidity;
        }

        Assertion assertion = buildXMLObject(Assertion.class, Assertion.DEFAULT_ELEMENT_NAME);
        assertion.setVersion(SAMLVersion.VERSION_20);
        String assertionId = "assertion-" + UUID.randomUUID().toString();
        assertion.setID(assertionId);
        assertion.setIssueInstant(issueInstant);

        // issuer
        Issuer issuer = buildXMLObject(Issuer.class, Issuer.DEFAULT_ELEMENT_NAME);
        assertion.setIssuer(issuer);
        issuer.setValue(issuerName);

        // conditions
        Conditions conditions = buildXMLObject(Conditions.class, Conditions.DEFAULT_ELEMENT_NAME);
        assertion.setConditions(conditions);
        DateTime notAfter = issueInstant.plusMinutes(validity);
        conditions.setNotBefore(issueInstant);
        conditions.setNotOnOrAfter(notAfter);

        if (null != inResponseTo) {
            conditions.getConditions()
                    .add(Saml2Util.buildXMLObject(OneTimeUse.class, OneTimeUse.DEFAULT_ELEMENT_NAME));
        }

        // audience restriction
        List<AudienceRestriction> audienceRestrictionList = conditions.getAudienceRestrictions();
        AudienceRestriction audienceRestriction = buildXMLObject(AudienceRestriction.class,
                AudienceRestriction.DEFAULT_ELEMENT_NAME);
        audienceRestrictionList.add(audienceRestriction);
        List<Audience> audiences = audienceRestriction.getAudiences();
        Audience audience = buildXMLObject(Audience.class, Audience.DEFAULT_ELEMENT_NAME);
        audiences.add(audience);
        audience.setAudienceURI(audienceUri);

        // subject
        Subject subject = buildXMLObject(Subject.class, Subject.DEFAULT_ELEMENT_NAME);
        assertion.setSubject(subject);
        NameID nameId = buildXMLObject(NameID.class, NameID.DEFAULT_ELEMENT_NAME);
        subject.setNameID(nameId);
        nameId.setValue(userId);

        // subject confirmation
        List<SubjectConfirmation> subjectConfirmations = subject.getSubjectConfirmations();
        SubjectConfirmation subjectConfirmation = buildXMLObject(SubjectConfirmation.class,
                SubjectConfirmation.DEFAULT_ELEMENT_NAME);
        subjectConfirmations.add(subjectConfirmation);
        subjectConfirmation.setMethod(SubjectConfirmation.METHOD_BEARER);
        if (null != inResponseTo) {
            SubjectConfirmationData subjectConfirmationData = buildXMLObject(SubjectConfirmationData.class,
                    SubjectConfirmationData.DEFAULT_ELEMENT_NAME);
            subjectConfirmation.setSubjectConfirmationData(subjectConfirmationData);
            subjectConfirmationData.setRecipient(recipient);
            subjectConfirmationData.setInResponseTo(inResponseTo);
            subjectConfirmationData.setNotBefore(issueInstant);
            subjectConfirmationData.setNotOnOrAfter(notAfter);
        }

        // authentication statement
        List<AuthnStatement> authnStatements = assertion.getAuthnStatements();
        AuthnStatement authnStatement = buildXMLObject(AuthnStatement.class, AuthnStatement.DEFAULT_ELEMENT_NAME);
        authnStatements.add(authnStatement);
        authnStatement.setAuthnInstant(issueInstant);
        AuthnContext authnContext = buildXMLObject(AuthnContext.class, AuthnContext.DEFAULT_ELEMENT_NAME);
        authnStatement.setAuthnContext(authnContext);

        AuthnContextClassRef authnContextClassRef = buildXMLObject(AuthnContextClassRef.class,
                AuthnContextClassRef.DEFAULT_ELEMENT_NAME);
        authnContextClassRef.setAuthnContextClassRef(authenticationPolicy.getUri());
        authnContext.setAuthnContextClassRef(authnContextClassRef);

        // attribute statement
        List<AttributeStatement> attributeStatements = assertion.getAttributeStatements();
        AttributeStatement attributeStatement = buildXMLObject(AttributeStatement.class,
                AttributeStatement.DEFAULT_ELEMENT_NAME);
        attributeStatements.add(attributeStatement);

        // get encryptor if needed
        Encrypter encrypter = getEncrypter(secretKey, publicKey);

        for (Map.Entry<String, be.fedict.eid.idp.common.Attribute> attributeEntry : attributes.entrySet()) {

            addAttribute(attributeEntry.getValue(), attributeStatement, encrypter);
        }

        return assertion;
    }

    private static Encrypter getEncrypter(SecretKey secretKey, PublicKey publicKey) {

        if (null != publicKey) {

            return getEncrypter(getAlgorithm(secretKey), secretKey, publicKey);

        } else if (null != secretKey) {

            return getEncrypter(getAlgorithm(secretKey), secretKey);

        } else {
            return null;
        }
    }

    private static String getAlgorithm(SecretKey secretKey) {

        if (null == secretKey) {
            return null;
        }

        if (secretKey.getAlgorithm().equals("AES")) {
            return EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128;
        } else {
            throw new RuntimeException("SecretKey algorithm: " + secretKey.getAlgorithm() + " not supported.");
        }

    }

    private static void addAttribute(be.fedict.eid.idp.common.Attribute attribute,
            AttributeStatement attributeStatement, Encrypter encrypter) {

        Attribute samlAttribute;
        switch (attribute.getAttributeType()) {

        case STRING:
            samlAttribute = getAttribute(attribute.getUri(), (String) attribute.getValue());
            break;
        case INTEGER:
            samlAttribute = getAttribute(attribute.getUri(), (Integer) attribute.getValue());
            break;
        case DATE:
            samlAttribute = getAttribute(attribute.getUri(), (GregorianCalendar) attribute.getValue());
            break;
        case BINARY:
            samlAttribute = getAttribute(attribute.getUri(), (byte[]) attribute.getValue());
            break;
        default:
            throw new RuntimeException("Attribute " + attribute.getUri() + " of type \""
                    + attribute.getAttributeType().getType() + " not supported!");
        }

        if (attribute.isEncrypted()) {

            // encrypted
            if (null == encrypter) {
                throw new RuntimeException("Encrypted attribute " + "needed but no encryption info was provided.");
            }

            if (null != attribute) {
                try {
                    attributeStatement.getEncryptedAttributes().add(encrypter.encrypt(samlAttribute));
                } catch (EncryptionException e) {
                    throw new RuntimeException(e);
                }
            }

        } else {

            if (null != attribute) {
                attributeStatement.getAttributes().add(samlAttribute);
            }
        }
    }

    @SuppressWarnings("unchecked")
    private static Attribute getAttribute(String attributeName, String attributeValue) {

        Attribute attribute = buildXMLObject(Attribute.class, Attribute.DEFAULT_ELEMENT_NAME);
        attribute.setName(attributeName);

        XMLObjectBuilder<XSString> builder = Configuration.getBuilderFactory().getBuilder(XSString.TYPE_NAME);
        XSString xmlAttributeValue = builder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME);
        xmlAttributeValue.setValue(attributeValue);
        attribute.getAttributeValues().add(xmlAttributeValue);
        return attribute;
    }

    @SuppressWarnings("unchecked")
    private static Attribute getAttribute(String attributeName, Integer attributeValue) {

        Attribute attribute = buildXMLObject(Attribute.class, Attribute.DEFAULT_ELEMENT_NAME);
        attribute.setName(attributeName);

        XMLObjectBuilder<XSInteger> builder = Configuration.getBuilderFactory().getBuilder(XSInteger.TYPE_NAME);
        XSInteger xmlAttributeValue = builder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSInteger.TYPE_NAME);
        xmlAttributeValue.setValue(attributeValue);
        attribute.getAttributeValues().add(xmlAttributeValue);
        return attribute;
    }

    @SuppressWarnings("unchecked")
    private static Attribute getAttribute(String attributeName, GregorianCalendar attributeValue) {

        Attribute attribute = buildXMLObject(Attribute.class, Attribute.DEFAULT_ELEMENT_NAME);
        attribute.setName(attributeName);

        XMLObjectBuilder<XSDateTime> builder = Configuration.getBuilderFactory().getBuilder(XSDateTime.TYPE_NAME);
        XSDateTime xmlAttributeValue = builder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME,
                XSDateTime.TYPE_NAME);

        // convert to Zulu timezone
        int day = attributeValue.get(Calendar.DAY_OF_MONTH);
        int month = attributeValue.get(Calendar.MONTH);
        int year = attributeValue.get(Calendar.YEAR);
        LOG.debug("day: " + day + " month: " + month + " year: " + year);
        DateTime zulu = new DateTime(year, month + 1, day, 0, 0, 0, 0, ISOChronology.getInstanceUTC());
        xmlAttributeValue.setValue(zulu);
        attribute.getAttributeValues().add(xmlAttributeValue);

        LOG.debug("XmlAttributeValue: " + xmlAttributeValue.getValue());

        return attribute;
    }

    @SuppressWarnings("unchecked")
    private static Attribute getAttribute(String attributeName, byte[] attributeValue) {

        Attribute attribute = buildXMLObject(Attribute.class, Attribute.DEFAULT_ELEMENT_NAME);
        attribute.setName(attributeName);

        XMLObjectBuilder<XSBase64Binary> builder = Configuration.getBuilderFactory()
                .getBuilder(XSBase64Binary.TYPE_NAME);
        XSBase64Binary xmlAttributeValue = builder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME,
                XSBase64Binary.TYPE_NAME);
        xmlAttributeValue.setValue(Base64.encodeBytes(attributeValue));
        attribute.getAttributeValues().add(xmlAttributeValue);
        return attribute;
    }

    /**
     * Returns a SAML v2.0 XML {@link Encrypter} for symmetric keys
     * 
     * @param algorithm
     *            secret key algorithm
     * @param secretKey
     *            the symmetric secret key
     * @return the encrypter
     */
    public static Encrypter getEncrypter(String algorithm, SecretKey secretKey) {

        LOG.debug("get encrypter: secret.algo=" + algorithm);

        KeyInfo keyInfo = buildXMLObject(KeyInfo.class, KeyInfo.DEFAULT_ELEMENT_NAME);

        BasicCredential encryptionCredential = new BasicCredential();
        encryptionCredential.setSecretKey(secretKey);

        EncryptionParameters encParams = new EncryptionParameters();
        encParams.setKeyInfoGenerator(new StaticKeyInfoGenerator(keyInfo));
        encParams.setAlgorithm(algorithm);
        encParams.setEncryptionCredential(encryptionCredential);

        List<KeyEncryptionParameters> kekParamsList = new ArrayList<KeyEncryptionParameters>();

        return new Encrypter(encParams, kekParamsList);
    }

    /**
     * Returns a SAML v2.0 XML {@link Decrypter} for symmetric keys
     * 
     * @param secretKey
     *            the symmetric secret key
     * @return the decrypter
     */
    public static Decrypter getDecrypter(SecretKey secretKey) {

        BasicCredential encryptionCredential = new BasicCredential();
        encryptionCredential.setSecretKey(secretKey);

        KeyInfoCredentialResolver keyResolver = new StaticKeyInfoCredentialResolver(encryptionCredential);

        return new Decrypter(keyResolver, null, null);
    }

    /**
     * Returns a SAML v2.0 XML {@link Encrypter} using symmetric keys
     * transported using an asymmetric key.
     * <p/>
     * The symmetric key is auto-generated AES-128 if not specified
     * 
     * @param algorithm
     *            secret key algorithm
     * @param secretKey
     *            the symmetric secret key or <code>null</code>
     * @param kekPublic
     *            the Key Encrypting RSA PublicKey
     * @return the encrypter
     */
    public static Encrypter getEncrypter(String algorithm, SecretKey secretKey, PublicKey kekPublic) {

        LOG.debug("get encrypter: secret.algo=" + algorithm + " public: " + kekPublic);
        BasicCredential keyEncryptionCredential = new BasicCredential();
        keyEncryptionCredential.setPublicKey(kekPublic);

        EncryptionParameters encParams = new EncryptionParameters();
        encParams.setAlgorithm(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128);
        if (null != secretKey) {

            BasicCredential encryptionCredential = new BasicCredential();
            encryptionCredential.setSecretKey(secretKey);

            KeyInfo keyInfo = buildXMLObject(KeyInfo.class, KeyInfo.DEFAULT_ELEMENT_NAME);

            encParams.setKeyInfoGenerator(new StaticKeyInfoGenerator(keyInfo));
            encParams.setAlgorithm(algorithm);
            encParams.setEncryptionCredential(encryptionCredential);
        }

        KeyEncryptionParameters kekParams = new KeyEncryptionParameters();
        kekParams.setEncryptionCredential(keyEncryptionCredential);
        kekParams.setAlgorithm(EncryptionConstants.ALGO_ID_KEYTRANSPORT_RSA15);
        KeyInfoGeneratorFactory kigf = Configuration.getGlobalSecurityConfiguration().getKeyInfoGeneratorManager()
                .getDefaultManager().getFactory(keyEncryptionCredential);
        kekParams.setKeyInfoGenerator(kigf.newInstance());

        Encrypter encrypter = new Encrypter(encParams, kekParams);
        encrypter.setKeyPlacement(Encrypter.KeyPlacement.PEER);

        return encrypter;
    }

    /**
     * Returns a SAML v2.0 XML {@link Decrypter} for symmetric keys transported
     * using an asymmetric key.
     * 
     * @param privateKey
     *            the RSA private key
     * @return the decrypter
     */
    public static Decrypter getDecrypter(PrivateKey privateKey) {

        BasicCredential decryptCredential = new BasicCredential();
        decryptCredential.setPrivateKey(privateKey);

        StaticKeyInfoCredentialResolver skicr = new StaticKeyInfoCredentialResolver(decryptCredential);

        return new Decrypter(null, skicr, new EncryptedElementTypeEncryptedKeyResolver());

    }

    /**
     * Validate specified SAML v2.0 Assertion.
     * <p/>
     * NOTE: validation of the XML Signature is not included!
     * 
     * @param assertion
     *            the assertion to validate
     * @param now
     *            current time, for validation of conditions
     * @param maxTimeOffset
     *            maximum time offset for assertion's conditions
     * @param audience
     *            expected audience
     * @param recipient
     *            recipient
     * @param requestId
     *            optional request ID
     * @param secretKey
     *            optional symmetric secret if encryption was used
     * @param privateKey
     *            optional asymmetric private key if encryption was used
     * @return {@link AuthenticationResponse} DO containing all available info
     *         on the authenticated subject.
     * @throws AssertionValidationException
     *             validation failed for some reason
     */
    public static AuthenticationResponse validateAssertion(Assertion assertion, DateTime now, int maxTimeOffset,
            String audience, String recipient, String requestId, SecretKey secretKey, PrivateKey privateKey)
            throws AssertionValidationException {

        LOG.debug("issuer: " + assertion.getIssuer().getValue());
        List<AuthnStatement> authnStatements = assertion.getAuthnStatements();
        if (authnStatements.isEmpty()) {
            throw new AssertionValidationException("missing SAML authn statement");
        }

        // validate assertion conditions
        validateConditions(now, maxTimeOffset, assertion.getConditions(), requestId, audience);

        // validate authn statement
        AuthnStatement authnStatement = assertion.getAuthnStatements().get(0);
        DateTime authenticationTime = authnStatement.getAuthnInstant();
        AuthnContext authnContext = authnStatement.getAuthnContext();
        if (null == authnContext) {
            throw new AssertionValidationException("missing SAML authn context");
        }
        AuthnContextClassRef authnContextClassRef = authnContext.getAuthnContextClassRef();
        if (null == authnContextClassRef) {
            throw new AssertionValidationException("missing SAML authn context ref");
        }

        // get authentication policy
        SamlAuthenticationPolicy authenticationPolicy = SamlAuthenticationPolicy
                .getAuthenticationPolicy(authnContextClassRef.getAuthnContextClassRef());

        Subject subject = assertion.getSubject();
        NameID nameId = subject.getNameID();

        // validate subject confirmation
        validateSubjectConfirmation(subject, requestId, recipient, now, maxTimeOffset);

        String identifier = nameId.getValue();
        Map<String, Object> attributeMap = new HashMap<String, Object>();

        List<AttributeStatement> attributeStatements = assertion.getAttributeStatements();
        if (!attributeStatements.isEmpty()) {

            AttributeStatement attributeStatement = attributeStatements.get(0);

            // normal attributes
            List<Attribute> attributes = attributeStatement.getAttributes();
            for (Attribute attribute : attributes) {

                processAttribute(attribute, attributeMap);
            }

            // encrypted attributes
            if (!attributeStatement.getEncryptedAttributes().isEmpty()) {

                Decrypter decrypter = getDecrypter(secretKey, privateKey);

                for (EncryptedAttribute encryptedAttribute : attributeStatement.getEncryptedAttributes()) {

                    try {
                        Attribute attribute = decrypter.decrypt(encryptedAttribute);
                        LOG.debug("decrypted attribute: " + attribute.getName());
                        processAttribute(attribute, attributeMap);

                    } catch (DecryptionException e) {
                        throw new AssertionValidationException(e);
                    }
                }
            }

        }

        return new AuthenticationResponse(authenticationTime, identifier, authenticationPolicy, attributeMap,
                assertion);
    }

    private static void validateConditions(DateTime now, int maxTimeOffset, Conditions conditions, String requestId,
            String audienceUri) throws AssertionValidationException {

        // time validation
        validateTime(now, conditions.getNotBefore(), conditions.getNotOnOrAfter(), maxTimeOffset);

        // audience restriction
        if (conditions.getAudienceRestrictions().isEmpty() || conditions.getAudienceRestrictions().size() != 1) {

            throw new AssertionValidationException("Expect exactly 1 audience restriction but got " + "0 or more");
        }
        AudienceRestriction audienceRestriction = conditions.getAudienceRestrictions().get(0);
        if (audienceRestriction.getAudiences().isEmpty() || audienceRestriction.getAudiences().size() != 1) {

            throw new AssertionValidationException("Expect exactly 1 audience but got 0 or more");
        }

        Audience audience = audienceRestriction.getAudiences().get(0);
        if (!audience.getAudienceURI().equals(audienceUri)) {
            LOG.debug("expected audience URI: " + audienceUri);
            LOG.debug("audience URI: " + audience.getAudienceURI());
            throw new AssertionValidationException("AudienceURI does not match expected recipient");
        }

        // OneTimeUse
        if (null == conditions.getOneTimeUse() && null != requestId) {

            throw new AssertionValidationException("Assertion is not one-time-use.");
        }
    }

    private static void validateSubjectConfirmation(Subject subject, String requestId, String recipient,
            DateTime now, int maxTimeOffset) throws AssertionValidationException {

        if (subject.getSubjectConfirmations().isEmpty() || subject.getSubjectConfirmations().size() != 1) {

            throw new AssertionValidationException("Expected exactly 1 SubjectConfirmation but got 0 or more");
        }
        SubjectConfirmation subjectConfirmation = subject.getSubjectConfirmations().get(0);

        // method
        if (!subjectConfirmation.getMethod().equals(SubjectConfirmation.METHOD_BEARER)) {

            throw new AssertionValidationException(
                    "Subjectconfirmation method: " + subjectConfirmation.getMethod() + " is not supported.");
        }

        if (null != requestId) {
            SubjectConfirmationData subjectConfirmationData = subjectConfirmation.getSubjectConfirmationData();

            // InResponseTo
            if (!subjectConfirmationData.getInResponseTo().equals(requestId)) {

                throw new AssertionValidationException(
                        "SubjectConfirmationData not belonging to " + "AuthnRequest!");
            }

            // recipient
            if (!subjectConfirmationData.getRecipient().equals(recipient)) {

                throw new AssertionValidationException(
                        "SubjectConfirmationData recipient does not " + "match expected recipient");
            }

            // time validation
            validateTime(now, subjectConfirmationData.getNotBefore(), subjectConfirmationData.getNotOnOrAfter(),
                    maxTimeOffset);
        }
    }

    private static void validateTime(DateTime now, DateTime notBefore, DateTime notOnOrAfter, int maxTimeOffset)
            throws AssertionValidationException {

        LOG.debug("now: " + now.toString());
        LOG.debug("notBefore: " + notBefore.toString());
        LOG.debug("notOnOrAfter : " + notOnOrAfter.toString());

        if (maxTimeOffset >= 0) {
            if (now.isBefore(notBefore)) {
                // time skew
                if (now.plusMinutes(maxTimeOffset).isBefore(notBefore)
                        || now.minusMinutes(maxTimeOffset).isAfter(notOnOrAfter)) {
                    throw new AssertionValidationException(
                            "SAML2 assertion validation: invalid SAML message timeframe");
                }
            } else if (now.isBefore(notBefore) || now.isAfter(notOnOrAfter)) {
                throw new AssertionValidationException(
                        "SAML2 assertion validation: invalid SAML message timeframe");
            }
        }
    }

    private static Decrypter getDecrypter(SecretKey secretKey, PrivateKey privateKey)
            throws AssertionValidationException {

        if (null == secretKey && null == privateKey) {
            throw new AssertionValidationException(
                    "Encrypted attributes were returned but " + "no decryption keys were specified.");
        }

        if (null != privateKey) {
            return Saml2Util.getDecrypter(privateKey);
        }

        return Saml2Util.getDecrypter(secretKey);
    }

    private static void processAttribute(Attribute attribute, Map<String, Object> attributeMap)
            throws AssertionValidationException {

        String attributeName = attribute.getName();

        if (attribute.getAttributeValues().get(0) instanceof XSString) {

            XSString attributeValue = (XSString) attribute.getAttributeValues().get(0);
            attributeMap.put(attributeName, attributeValue.getValue());

        } else if (attribute.getAttributeValues().get(0) instanceof XSInteger) {

            XSInteger attributeValue = (XSInteger) attribute.getAttributeValues().get(0);
            attributeMap.put(attributeName, attributeValue.getValue());

        } else if (attribute.getAttributeValues().get(0) instanceof XSDateTime) {

            XSDateTime attributeValue = (XSDateTime) attribute.getAttributeValues().get(0);
            attributeMap.put(attributeName, attributeValue.getValue().toDateTime(ISOChronology.getInstanceUTC()));

        } else if (attribute.getAttributeValues().get(0) instanceof XSBase64Binary) {

            XSBase64Binary attributeValue = (XSBase64Binary) attribute.getAttributeValues().get(0);
            attributeMap.put(attributeName, Base64.decode(attributeValue.getValue()));

        } else {
            throw new AssertionValidationException("Unsupported attribute of " + "type: "
                    + attribute.getAttributeValues().get(0).getClass().getName());
        }
    }

    /**
     * Construct an opensaml SAML object of specified class type and element
     * name
     * 
     * @param clazz
     *            opensaml class type
     * @param objectQName
     *            QName
     * @param <T>
     *            opensaml object type
     * @return opensaml object.
     */
    public static <T extends XMLObject> T buildXMLObject(Class<T> clazz, QName objectQName) {

        @SuppressWarnings("unchecked")
        XMLObjectBuilder<T> builder = Configuration.getBuilderFactory().getBuilder(objectQName);
        if (builder == null) {
            throw new RuntimeException("Unable to retrieve builder for object QName " + objectQName);
        }

        return builder.buildObject(objectQName);
    }

    /**
     * Sign specified signable SAML object and return marshalled element.
     * 
     * @param xmlObject
     *            opensaml XML object where object to be signed resides in, this
     *            can be equal to the object to sign
     * @param signableSAMLObject
     *            opensaml object to sign
     * @param privateKeyEntry
     *            key entry used to sign
     * @return marshalled, signed xml element.
     */
    public static Element signAsElement(XMLObject xmlObject, SignableSAMLObject signableSAMLObject,
            KeyStore.PrivateKeyEntry privateKeyEntry) {

        XMLObject returnedXmlObject = sign(xmlObject, signableSAMLObject, privateKeyEntry);

        return marshall(returnedXmlObject);
    }

    /**
     * Sign specified opensaml signable object with specifiied key entry.
     * 
     * @param signableSAMLObject
     *            saml object to sign
     * @param privateKeyEntry
     *            key entry to sign with
     * @return signed saml object
     */
    public static XMLObject sign(SignableSAMLObject signableSAMLObject, KeyStore.PrivateKeyEntry privateKeyEntry) {

        return sign(signableSAMLObject, signableSAMLObject, privateKeyEntry);

    }

    private static XMLObject sign(XMLObject xmlObject, SignableSAMLObject signableSAMLObject,
            KeyStore.PrivateKeyEntry privateKeyEntry) {
        XMLObjectBuilderFactory builderFactory = Configuration.getBuilderFactory();
        SignatureBuilder signatureBuilder = (SignatureBuilder) builderFactory
                .getBuilder(Signature.DEFAULT_ELEMENT_NAME);
        Signature signature = signatureBuilder.buildObject();
        signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);

        String algorithm = privateKeyEntry.getPrivateKey().getAlgorithm();
        if ("RSA".equals(algorithm)) {
            signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA);
        } else if ("DSA".equals(algorithm)) {
            signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_DSA);
        }

        List<X509Certificate> certificateChain = new LinkedList<X509Certificate>();
        for (java.security.cert.Certificate certificate : privateKeyEntry.getCertificateChain()) {
            certificateChain.add((X509Certificate) certificate);
        }

        // add certificate chain as keyinfo
        KeyInfo keyInfo = buildXMLObject(KeyInfo.class, KeyInfo.DEFAULT_ELEMENT_NAME);
        try {
            for (X509Certificate certificate : certificateChain) {
                KeyInfoHelper.addCertificate(keyInfo, certificate);
            }
        } catch (CertificateEncodingException e) {
            throw new RuntimeException("opensaml2 certificate encoding error: " + e.getMessage(), e);
        }
        signature.setKeyInfo(keyInfo);

        BasicX509Credential signingCredential = new BasicX509Credential();
        signingCredential.setPrivateKey(privateKeyEntry.getPrivateKey());
        signingCredential.setEntityCertificateChain(certificateChain);

        // enable adding the cert.chain as KeyInfo
        X509KeyInfoGeneratorFactory factory = (X509KeyInfoGeneratorFactory) org.opensaml.xml.Configuration
                .getGlobalSecurityConfiguration().getKeyInfoGeneratorManager().getDefaultManager()
                .getFactory(signingCredential);
        factory.setEmitEntityCertificateChain(true);

        signature.setSigningCredential(signingCredential);
        signableSAMLObject.setSignature(signature);

        // Marshall so it has an XML representation.
        marshall(xmlObject);

        // Sign after marshaling so we can add a signature to the XML
        // representation.
        try {
            Signer.signObject(signature);
        } catch (SignatureException e) {
            throw new RuntimeException("opensaml2 signing error: " + e.getMessage(), e);
        }
        return xmlObject;
    }

    /**
     * Validate the specified opensaml XML Signature
     * 
     * @param signature
     *            the XML signature
     * @return list of {@link X509Certificate}'s in the XML signature
     * @throws CertificateException
     *             something went wrong extracting the certificates from the XML
     *             Signature.
     * @throws ValidationException
     *             validation failed
     */
    public static List<X509Certificate> validateSignature(Signature signature)
            throws CertificateException, ValidationException {

        List<X509Certificate> certChain = KeyInfoHelper.getCertificates(signature.getKeyInfo());

        SAMLSignatureProfileValidator pv = new SAMLSignatureProfileValidator();
        pv.validate(signature);
        BasicX509Credential credential = new BasicX509Credential();
        credential.setPublicKey(getEndCertificate(certChain).getPublicKey());
        SignatureValidator sigValidator = new SignatureValidator(credential);
        sigValidator.validate(signature);

        return certChain;
    }

    /**
     * Get end {@link X509Certificate} from specified chain.
     * 
     * @param certChain
     *            the {@link X509Certificate} chain.
     * @return the end {@link X509Certificate}.
     */
    public static X509Certificate getEndCertificate(List<X509Certificate> certChain) {

        if (certChain.size() == 1) {
            return certChain.get(0);
        }

        if (isSelfSigned(certChain.get(0))) {
            return certChain.get(certChain.size() - 1);
        } else {
            return certChain.get(0);
        }

    }

    private static boolean isSelfSigned(X509Certificate certificate) {

        return certificate.getIssuerX500Principal().equals(certificate.getSubjectX500Principal());
    }

    /**
     * Marhsall the opensaml {@link XMLObject} to a DOM {@link Element}
     * 
     * @param xmlObject
     *            the XML object
     * @return marshalled DOM element
     */
    public static Element marshall(XMLObject xmlObject) {

        MarshallerFactory marshallerFactory = Configuration.getMarshallerFactory();
        Marshaller marshaller = marshallerFactory.getMarshaller(xmlObject);

        try {
            return marshaller.marshall(xmlObject);
        } catch (MarshallingException e) {
            throw new RuntimeException("opensaml2 marshalling error: " + e.getMessage(), e);
        }
    }

    /**
     * Write the DOM {@link Document} to specified {@link OutputStream}
     * 
     * @param document
     *            DOM document
     * @param documentOutputStream
     *            output stream
     * @throws TransformerFactoryConfigurationError
     *             transformer config error
     * @throws TransformerException
     *             transformer error
     * @throws IOException
     *             IO error
     */
    public static void writeDocument(Document document, OutputStream documentOutputStream)
            throws TransformerFactoryConfigurationError, TransformerException, IOException {
        Result result = new StreamResult(documentOutputStream);
        Transformer xformer = TransformerFactory.newInstance().newTransformer();
        Source source = new DOMSource(document);
        xformer.transform(source, result);
    }

    /**
     * Parses the given string to a DOM object.
     * 
     * @param documentString
     *            the DOM as string
     * @return the DOM
     */
    public static Document parseDocument(String documentString) {

        try {
            DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
            domFactory.setNamespaceAware(true);
            DocumentBuilder domBuilder = domFactory.newDocumentBuilder();
            StringReader stringReader = new StringReader(documentString);
            InputSource inputSource = new InputSource(stringReader);
            return domBuilder.parse(inputSource);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (SAXException e) {
            throw new RuntimeException(e);
        } catch (ParserConfigurationException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Convert specified DOM {@link Node} to a string representation
     * 
     * @param domNode
     *            the DOM node
     * @param indent
     *            indent or not
     * @return the string representation of the DOM node
     */
    public static String domToString(Node domNode, boolean indent) {

        try {
            Source source = new DOMSource(domNode);
            StringWriter stringWriter = new StringWriter();
            Result result = new StreamResult(stringWriter);

            TransformerFactory transformerFactory = TransformerFactory.newInstance();
            Transformer transformer = transformerFactory.newTransformer();

            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
            transformer.setOutputProperty(OutputKeys.INDENT, indent ? "yes" : "no");
            transformer.transform(source, result);

            return stringWriter.toString();
        } catch (TransformerException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Convert specified opensaml {@link XMLObject} to specified JAXB type.
     * 
     * @param openSAMLObject
     *            the opensaml XML object.
     * @param wsType
     *            the JAXB class
     * @param <T>
     *            the JAXB type
     * @return JAXB representation of the opensaml xml object.
     */
    @SuppressWarnings("unchecked")
    public static <T> T toJAXB(final XMLObject openSAMLObject, Class<T> wsType) {

        try {
            Element element = Configuration.getMarshallerFactory().getMarshaller(openSAMLObject)
                    .marshall(openSAMLObject);

            return ((JAXBElement<T>) JAXBContext.newInstance(wsType).createUnmarshaller().unmarshal(element))
                    .getValue();
        } catch (MarshallingException e) {
            throw new RuntimeException("Marshaling from OpenSAML object failed.", e);
        } catch (JAXBException e) {
            throw new RuntimeException("Unmarshaling to JAXB object failed.", e);
        }
    }

    /**
     * Convert specified JAXB object to an opensaml XML object
     * 
     * @param wsObject
     *            JAXB object
     * @param wsType
     *            JAXB class
     * @param samlElementName
     *            opensaml QName
     * @param <F>
     *            JAXB type
     * @param <T>
     *            opensaml type
     * @return opensaml {@link XMLObject}
     */
    @SuppressWarnings({ "unchecked" })
    public static <F, T extends XMLObject> T toSAML(final F wsObject, Class<F> wsType, QName samlElementName) {

        try {
            Document root = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
            JAXBContext.newInstance(wsType).createMarshaller()
                    .marshal(new JAXBElement<F>(samlElementName, wsType, wsObject), root);

            return (T) unmarshall(root.getDocumentElement());
        } catch (ParserConfigurationException e) {
            throw new RuntimeException("Default parser " + "configuration failed.", e);
        } catch (JAXBException e) {
            throw new RuntimeException("Marshaling to OpenSAML " + "object failed.", e);
        }
    }

    /**
     * Unmarshall specified DOM {@link Element} to an opensaml {@link XMLObject}
     * 
     * @param xmlElement
     *            DOM element
     * @param <X>
     *            opensaml type
     * @return the opensaml object.
     */
    @SuppressWarnings({ "unchecked" })
    public static <X extends XMLObject> X unmarshall(Element xmlElement) {

        UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();
        Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(xmlElement);

        try {
            return (X) unmarshaller.unmarshall(xmlElement);
        } catch (UnmarshallingException e) {
            throw new RuntimeException("opensaml2 unmarshalling " + "error: " + e.getMessage(), e);
        }
    }

    /**
     * Find {@link Node} specified with XPath expression in {@link Document}
     * 
     * @param document
     *            document to search
     * @param xpath
     *            XPath to to be found Node
     * @return Node or <code>null</code> if not found.
     */
    public static Node find(Document document, String xpath) {

        try {
            return XPathAPI.selectSingleNode(document, xpath, getNSElement(document));
        } catch (TransformerException e) {
            throw new RuntimeException("XPath error: " + e.getMessage());
        }
    }

    private static Element getNSElement(Document document) {

        Element nsElement = document.createElement("nsElement");
        nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:soap",
                "http://schemas.xmlsoap.org/soap/envelope/");
        nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", "http://www.w3.org/2000/09/xmldsig#");
        nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol");
        nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion");

        return nsElement;
    }

    /**
     * Sign DOM document
     * 
     * @param documentElement
     *            document to be signed
     * @param nextSibling
     *            next sibling in document, dsig is added before this one
     * @param identity
     *            Identity to sign with
     * @throws NoSuchAlgorithmException
     *             signing algorithm not found
     * @throws InvalidAlgorithmParameterException
     *             invalid signing algo param
     * @throws MarshalException
     *             error marshalling signature
     * @throws XMLSignatureException
     *             error during signing
     */
    public static void signDocument(Element documentElement, Node nextSibling, KeyStore.PrivateKeyEntry identity)
            throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, MarshalException,
            XMLSignatureException {

        // get document ID
        String documentId = documentElement.getAttribute("ID");
        LOG.debug("document ID=" + documentId);

        // fix for recent versions of Apache xmlsec.
        documentElement.setIdAttribute("ID", true);

        XMLSignatureFactory signatureFactory = XMLSignatureFactory.getInstance("DOM");

        XMLSignContext signContext = new DOMSignContext(identity.getPrivateKey(), documentElement, nextSibling);
        signContext.putNamespacePrefix(javax.xml.crypto.dsig.XMLSignature.XMLNS, "ds");
        javax.xml.crypto.dsig.DigestMethod digestMethod = signatureFactory
                .newDigestMethod(javax.xml.crypto.dsig.DigestMethod.SHA1, null);

        List<javax.xml.crypto.dsig.Transform> transforms = new LinkedList<javax.xml.crypto.dsig.Transform>();
        transforms.add(signatureFactory.newTransform(javax.xml.crypto.dsig.Transform.ENVELOPED,
                (TransformParameterSpec) null));
        javax.xml.crypto.dsig.Transform exclusiveTransform = signatureFactory
                .newTransform(CanonicalizationMethod.EXCLUSIVE, (TransformParameterSpec) null);
        transforms.add(exclusiveTransform);

        Reference reference = signatureFactory.newReference("#" + documentId, digestMethod, transforms, null, null);

        SignatureMethod signatureMethod = signatureFactory.newSignatureMethod(SignatureMethod.RSA_SHA1, null);
        CanonicalizationMethod canonicalizationMethod = signatureFactory
                .newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE, (C14NMethodParameterSpec) null);
        SignedInfo signedInfo = signatureFactory.newSignedInfo(canonicalizationMethod, signatureMethod,
                Collections.singletonList(reference));

        List<Object> keyInfoContent = new LinkedList<Object>();
        KeyInfoFactory keyInfoFactory = KeyInfoFactory.getInstance();
        List<Object> x509DataObjects = new LinkedList<Object>();

        for (X509Certificate certificate : Saml2Util.getCertificateChain(identity)) {
            x509DataObjects.add(certificate);
        }
        javax.xml.crypto.dsig.keyinfo.X509Data x509Data = keyInfoFactory.newX509Data(x509DataObjects);
        keyInfoContent.add(x509Data);
        javax.xml.crypto.dsig.keyinfo.KeyInfo keyInfo = keyInfoFactory.newKeyInfo(keyInfoContent);

        javax.xml.crypto.dsig.XMLSignature xmlSignature = signatureFactory.newXMLSignature(signedInfo, keyInfo);
        xmlSignature.sign(signContext);
    }

}