org.owasp.webscarab.plugin.saml.SamlModel.java Source code

Java tutorial

Introduction

Here is the source code for org.owasp.webscarab.plugin.saml.SamlModel.java

Source

/**
 * *********************************************************************
 *
 * $CVSHeader$
 *
 * This file is part of WebScarab, an Open Web Application Security Project
 * utility. For details, please see http://www.owasp.org/
 *
 * Copyright (c) 2010 FedICT Copyright (c) 2010 Frank Cornelis
 * <info@frankcornelis.be>
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * Getting Source ==============
 *
 * Source for this application is maintained at Sourceforge.net, a repository
 * for free software projects.
 *
 * For details, please see http://www.sourceforge.net/projects/owasp
 *
 */
package org.owasp.webscarab.plugin.saml;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.security.PrivateKey;
import java.security.Key;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.xml.security.Init;
import org.apache.xml.security.encryption.XMLCipher;
import org.apache.xml.security.encryption.XMLEncryptionException;
import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.keys.KeyInfo;
import org.apache.xml.security.keys.content.X509Data;
import org.apache.xml.security.keys.content.x509.XMLX509Certificate;
import org.apache.xml.security.keys.keyresolver.KeyResolverException;
import org.apache.xml.security.signature.Manifest;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.signature.XMLSignatureException;
import org.bouncycastle.util.encoders.Hex;
import org.htmlparser.tags.FormTag;
import org.htmlparser.util.NodeIterator;
import org.htmlparser.util.ParserException;
import org.owasp.webscarab.model.ConversationID;
import org.owasp.webscarab.model.ConversationModel;
import org.owasp.webscarab.model.FilteredConversationModel;
import org.owasp.webscarab.model.FrameworkModel;
import org.owasp.webscarab.model.HttpUrl;
import org.owasp.webscarab.model.NamedValue;
import org.owasp.webscarab.model.Response;
import org.owasp.webscarab.parser.Parser;
import org.owasp.webscarab.plugin.AbstractPluginModel;
import org.owasp.webscarab.util.Encoding;
import org.owasp.webscarab.util.MRUCache;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 *
 * @author Frank Cornelis
 */
public class SamlModel extends AbstractPluginModel {

    private Logger _logger = Logger.getLogger(getClass().getName());
    private final FrameworkModel model;
    private final ConversationModel samlConversationModel;
    private final MRUCache<ConversationID, Document> parsedDocuments;
    private final DocumentBuilder builder;

    public SamlModel(FrameworkModel model) {
        this.model = model;
        this.samlConversationModel = new FilteredConversationModel(model, model.getConversationModel()) {
            @Override
            public boolean shouldFilter(ConversationID id) {
                return !isSAMLMessage(id);
            }
        };
        this.parsedDocuments = new MRUCache<ConversationID, Document>(8);

        DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
        builderFactory.setNamespaceAware(true);
        try {
            this.builder = builderFactory.newDocumentBuilder();
        } catch (ParserConfigurationException ex) {
            throw new RuntimeException("parser config error: " + ex.getMessage(), ex);
        }
    }

    static {
        Init.init();
    }

    public void setSAMLResponse(ConversationID id, String encodedSamlResponse) {
        setSAMLResponse(id, encodedSamlResponse, false);
    }

    /**
     * Mark this conversation message as being a SAML Response.
     *
     * @param id
     * @param encodedSamlResponse
     */
    public void setSAMLResponse(ConversationID id, String encodedSamlResponse, boolean deflate) {
        this.model.setConversationProperty(id, "SAML-TYPE", "Response");
        this.model.setConversationProperty(id, "SAML-MESSAGE", encodedSamlResponse);
        if (deflate) {
            this.model.setConversationProperty(id, "SAML-DEFLATE", Boolean.TRUE.toString());
        }
    }

    public void setRelayState(ConversationID id, String relayState) {
        this.model.setConversationProperty(id, "SAML-RELAY-STATE", relayState);
    }

    public String getRelayState(ConversationID id) {
        String relayState = this.model.getConversationProperty(id, "SAML-RELAY-STATE");
        return relayState;
    }

    public String getEncodedSAMLMessage(ConversationID id) {
        String encodedSamlMessage = this.model.getConversationProperty(id, "SAML-MESSAGE");
        String urlDecodedSamlMessage = Encoding.urlDecode(encodedSamlMessage);
        return urlDecodedSamlMessage;
    }

    public String getSAMLMessage(ConversationID id) {
        String samlMessage = this.model.getConversationProperty(id, "SAML-MESSAGE");
        return samlMessage;
    }

    public ConversationID findCorrespondingHTMLFormConversation(ConversationID samlId) {
        ConversationModel conversationModel = this.model.getConversationModel();
        HttpUrl samlHttpUrl = conversationModel.getRequestUrl(samlId);
        int samlConversationIndex = conversationModel.getIndexOfConversation(samlId);
        for (int conversationIndex = samlConversationIndex - 1; conversationIndex >= 0; conversationIndex--) {
            ConversationID id = conversationModel.getConversationAt(conversationIndex);
            Response response = conversationModel.getResponse(id);
            HttpUrl httpUrl = conversationModel.getRequestUrl(id);
            Object parsedContent = Parser.parse(httpUrl, response);
            if (null == parsedContent) {
                continue;
            }
            if (false == parsedContent instanceof org.htmlparser.util.NodeList) {
                continue;
            }
            org.htmlparser.util.NodeList htmlNodeList = (org.htmlparser.util.NodeList) parsedContent;
            org.htmlparser.util.NodeList forms = htmlNodeList.searchFor(FormTag.class);
            try {
                for (NodeIterator ni = forms.elements(); ni.hasMoreNodes();) {
                    FormTag form = (FormTag) ni.nextNode();
                    String formAction = form.getAttribute("action");
                    HttpUrl formActionHttpUrl = new HttpUrl(formAction);
                    if (samlHttpUrl.equals(formActionHttpUrl)) {
                        return id;
                    }
                }
            } catch (ParserException ex) {
                this._logger.log(Level.WARNING, "Looking for forms, got ''{0}''", ex);
            } catch (MalformedURLException ex) {
                this._logger.log(Level.WARNING, "Malformed action url: {0}", ex.getMessage());
            }
        }
        return null;
    }

    public byte[] getResponseContent(ConversationID id) {
        ConversationModel conversationModel = this.model.getConversationModel();
        Response response = conversationModel.getResponse(id);
        byte[] content = response.getContent();
        return content;
    }

    public boolean isOverSSL(ConversationID id) {
        ConversationModel conversationModel = this.model.getConversationModel();
        HttpUrl httpUrl = conversationModel.getRequestUrl(id);
        String scheme = httpUrl.getScheme();
        if ("https".equals(scheme)) {
            return true;
        }
        return false;
    }

    public String getDecodedSAMLMessage(ConversationID id) {
        String encodedSAMLMessage = getEncodedSAMLMessage(id);
        String decodedSAMLMessage = getDecodedSAMLMessage(encodedSAMLMessage, id);
        return decodedSAMLMessage;
    }

    public String getDecodedSAMLMessage(String encodedSamlMessage, ConversationID id) {
        /*
         * Cannot use org.owasp.webscarab.util.Encoding here as SAML tickets not
         * always come with line-breaks.
         */

        String deflate = this.model.getConversationProperty(id, "SAML-DEFLATE");
        if (null != deflate) {
            _logger.fine("inflating SAML message");
            byte[] deflated = Base64.decodeBase64(encodedSamlMessage);
            try {
                Inflater inflater = new Inflater(true);
                InflaterInputStream inflaterInputStream = new InflaterInputStream(
                        new ByteArrayInputStream(deflated), inflater);
                return new String(IOUtils.toByteArray(inflaterInputStream));
            } catch (IOException ex) {
                return "[ERROR INFLATING SAML MESSAGE]: " + ex.getMessage();
            }
        }

        String decodedSamlMessage = new String(Base64.decodeBase64(encodedSamlMessage));

        return decodedSamlMessage;
    }

    public static final int SAML_VERSION_2 = 2;
    public static final int SAML_VERSION_1_1 = 1;

    private Document getSAMLDocument(ConversationID id) {
        Document document = (Document) this.parsedDocuments.get(id);
        if (null != document) {
            return document;
        }

        String encodedSamlMessage = getEncodedSAMLMessage(id);
        String decodedSamlMessage = getDecodedSAMLMessage(encodedSamlMessage, id);
        ByteArrayInputStream inputStream = new ByteArrayInputStream(decodedSamlMessage.getBytes());

        try {
            document = this.builder.parse(inputStream);
            this.parsedDocuments.put(id, document);
            return document;
        } catch (SAXException ex) {
            return null;
        } catch (IOException ex) {
            return null;
        }
    }

    public int getSAMLVersion(ConversationID id) {
        Document document = getSAMLDocument(id);
        if (null == document) {
            return 0;
        }
        NodeList saml1ResponseNodeList = document.getElementsByTagNameNS("urn:oasis:names:tc:SAML:1.0:protocol",
                "Response");
        if (0 != saml1ResponseNodeList.getLength()) {
            return SAML_VERSION_1_1;
        }
        NodeList saml2AuthnRequestNodeList = document.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:protocol",
                "AuthnRequest");
        if (0 != saml2AuthnRequestNodeList.getLength()) {
            return SAML_VERSION_2;
        }
        NodeList saml2ResponseNodeList = document.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:protocol",
                "Response");
        if (0 != saml2ResponseNodeList.getLength()) {
            return SAML_VERSION_2;
        }
        return 0;
    }

    public boolean hasDestinationIndication(ConversationID id) {
        Document document = getSAMLDocument(id);
        if (null == document) {
            return false;
        }
        NodeList saml2ResponseNodeList = document.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:protocol",
                "Response");
        if (0 != saml2ResponseNodeList.getLength()) {
            return hasDestinationIndicationSaml2Response((Element) saml2ResponseNodeList.item(0));
        }
        NodeList saml2AuthnRequestNodeList = document.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:protocol",
                "AuthnRequest");
        if (0 != saml2AuthnRequestNodeList.getLength()) {
            return hasDestinationIndicationSaml2AuthnRequest((Element) saml2AuthnRequestNodeList.item(0));
        }
        NodeList saml1ResponseNodeList = document.getElementsByTagNameNS("urn:oasis:names:tc:SAML:1.0:protocol",
                "Response");
        if (0 != saml1ResponseNodeList.getLength()) {
            return hasDestinationIndicationSaml1Response((Element) saml1ResponseNodeList.item(0));
        }
        return false;
    }

    public static Element findProtocolSignatureElement(Document document) {
        Element documentElement = document.getDocumentElement();
        NodeList documentChildNodes = documentElement.getChildNodes();
        int documentNodeCount = documentChildNodes.getLength();
        for (int nodeIdx = 0; nodeIdx < documentNodeCount; nodeIdx++) {
            Node node = documentChildNodes.item(nodeIdx);
            if (Node.ELEMENT_NODE == node.getNodeType()) {
                Element element = (Element) node;
                if (false == "http://www.w3.org/2000/09/xmldsig#".equals(element.getNamespaceURI())) {
                    continue;
                }
                if (false == "Signature".equals(element.getLocalName())) {
                    continue;
                }
                return element;
            }
        }
        return null;
    }

    public static Element findAssertionSignatureElement(Document document) {
        NodeList assertionNodeList = document.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion",
                "Assertion");
        if (0 == assertionNodeList.getLength()) {
            assertionNodeList = document.getElementsByTagNameNS("urn:oasis:names:tc:SAML:1.0:assertion",
                    "Assertion");
            if (0 == assertionNodeList.getLength()) {
                return null;
            }
        }
        Node assertionNode = assertionNodeList.item(0);
        NodeList assertionChildrenNodeList = assertionNode.getChildNodes();
        int assertionChildrenNodeCount = assertionChildrenNodeList.getLength();
        for (int nodeIdx = 0; nodeIdx < assertionChildrenNodeCount; nodeIdx++) {
            Node node = assertionChildrenNodeList.item(nodeIdx);
            if (Node.ELEMENT_NODE == node.getNodeType()) {
                Element element = (Element) node;
                if (false == "http://www.w3.org/2000/09/xmldsig#".equals(element.getNamespaceURI())) {
                    continue;
                }
                if (false == "Signature".equals(element.getLocalName())) {
                    continue;
                }
                return element;
            }
        }
        return null;
    }

    public List<X509Certificate> verifySAMLProtocolSignature(ConversationID id) throws SamlSignatureException {
        Document document = getSAMLDocument(id);
        if (null == document) {
            throw new SamlSignatureException("DOM parser error");
        }
        Element protocolSignatureElement = findProtocolSignatureElement(document);
        if (null == protocolSignatureElement) {
            throw new SamlSignatureException("No protocol XML signature present");
        }
        XMLSignature xmlSignature;
        try {
            xmlSignature = new XMLSignature(protocolSignatureElement, "");
        } catch (XMLSignatureException ex) {
            throw new SamlSignatureException("Invalid protocol XML Signature", ex);
        } catch (XMLSecurityException ex) {
            throw new SamlSignatureException("XML security error", ex);
        }
        KeyInfo keyInfo = xmlSignature.getKeyInfo();
        X509Certificate signingCertificate;
        try {
            signingCertificate = keyInfo.getX509Certificate();
        } catch (KeyResolverException ex) {
            throw new SamlSignatureException("X509 certificate not present", ex);
        }
        boolean signatureValid;
        try {
            signatureValid = xmlSignature.checkSignatureValue(signingCertificate);
        } catch (XMLSignatureException ex) {
            throw new SamlSignatureException("signature error: " + ex.getMessage());
        }
        if (false == signatureValid) {
            throw new SamlSignatureException("invalid");
        }
        List<X509Certificate> certificateChain = new LinkedList<X509Certificate>();
        if (false == keyInfo.containsX509Data()) {
            throw new SamlSignatureException("no X509 data in KeyInfo");
        }
        for (int x509DataItemIdx = 0; x509DataItemIdx < keyInfo.lengthX509Data(); x509DataItemIdx++) {
            try {
                X509Data x509Data = keyInfo.itemX509Data(x509DataItemIdx);
                if (false == x509Data.containsCertificate()) {
                    continue;
                }
                int certificateCount = x509Data.lengthCertificate();
                for (int certificateIdx = 0; certificateIdx < certificateCount; certificateIdx++) {
                    XMLX509Certificate xmlX509Certificate = x509Data.itemCertificate(certificateIdx);
                    X509Certificate certificate = xmlX509Certificate.getX509Certificate();
                    certificateChain.add(certificate);
                }
            } catch (XMLSecurityException ex) {
                throw new SamlSignatureException("X509 data error", ex);
            }
        }
        return certificateChain;
    }

    public boolean isSAMLMessage(ConversationID id) {
        return this.model.getConversationProperty(id, "SAML-TYPE") != null;
    }

    public boolean isSAMLResponse(ConversationID id) {
        if ("Response".equals(this.model.getConversationProperty(id, "SAML-TYPE"))) {
            return true;
        }
        return false;
    }

    public ConversationModel getSamlConversationModel() {
        return this.samlConversationModel;
    }

    public String getSAMLType(ConversationID conversationId) {
        String samlType = this.model.getConversationProperty(conversationId, "SAML-TYPE");
        return samlType;
    }

    public void setSAMLRequest(ConversationID id, String encodedSamlRequest, boolean deflate) {
        this.model.setConversationProperty(id, "SAML-TYPE", "Request");
        this.model.setConversationProperty(id, "SAML-MESSAGE", encodedSamlRequest);
        if (deflate) {
            this.model.setConversationProperty(id, "SAML-DEFLATE", Boolean.TRUE.toString());
        }
    }

    public void setSAMLRequest(ConversationID id, String encodedSamlRequest) {
        setSAMLRequest(id, encodedSamlRequest, false);
    }

    private boolean hasDestinationIndicationSaml2Response(Element responseElement) {
        if (null != responseElement.getAttributeNode("Destination")) {
            return true;
        }
        NodeList assertionNodeList = responseElement.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion",
                "Assertion");
        if (0 == assertionNodeList.getLength()) {
            return false;
        }
        Element assertionElement = (Element) assertionNodeList.item(0);
        NodeList audienceNodeList = assertionElement.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion",
                "Audience");
        if (0 != audienceNodeList.getLength()) {
            return true;
        }
        NodeList subjectConfirmationDataNodeList = assertionElement
                .getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion", "SubjectConfirmationData");
        if (0 == subjectConfirmationDataNodeList.getLength()) {
            return false;
        }
        Element subjectConfirmationDataElement = (Element) subjectConfirmationDataNodeList.item(0);
        if (null != subjectConfirmationDataElement.getAttributeNode("Recipient")) {
            return true;
        }
        if (null != subjectConfirmationDataElement.getAttributeNode("Address")) {
            return true;
        }
        return false;
    }

    private boolean hasDestinationIndicationSaml1Response(Element responseElement) {
        if (null != responseElement.getAttributeNode("Recipient")) {
            return true;
        }
        NodeList assertionNodeList = responseElement.getElementsByTagNameNS("urn:oasis:names:tc:SAML:1.0:assertion",
                "Assertion");
        if (0 == assertionNodeList.getLength()) {
            return false;
        }
        Element assertionElement = (Element) assertionNodeList.item(0);
        NodeList audienceNodeList = assertionElement.getElementsByTagNameNS("urn:oasis:names:tc:SAML:1.0:assertion",
                "Audience");
        if (0 != audienceNodeList.getLength()) {
            return true;
        }
        return false;
    }

    private boolean hasDestinationIndicationSaml2AuthnRequest(Element authnRequestElement) {
        if (null != authnRequestElement.getAttributeNode("Destination")) {
            return true;
        }
        return false;
    }

    public boolean protocolSignatureDigestsAssertions(ConversationID id) {
        Document document = getSAMLDocument(id);
        if (null == document) {
            return false;
        }
        Element protocolSignatureElement = findProtocolSignatureElement(document);
        if (null == protocolSignatureElement) {
            return false;
        }

        NodeList saml2AssertionNodeList = document.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion",
                "Assertion");
        if (0 != saml2AssertionNodeList.getLength()) {
            try {
                return isDigested(saml2AssertionNodeList, protocolSignatureElement);
            } catch (XMLSignatureException ex) {
                this._logger.log(Level.WARNING, "XML signature error: {0}", ex.getMessage());
            } catch (XMLSecurityException ex) {
                this._logger.log(Level.WARNING, "XML security error: {0}", ex.getMessage());
            }
        }

        NodeList saml1AssertionNodeList = document.getElementsByTagNameNS("urn:oasis:names:tc:SAML:1.0:assertion",
                "Assertion");
        if (0 != saml1AssertionNodeList.getLength()) {
            try {
                return isDigested(saml1AssertionNodeList, protocolSignatureElement);
            } catch (XMLSignatureException ex) {
                this._logger.log(Level.WARNING, "XML signature error: {0}", ex.getMessage());
            } catch (XMLSecurityException ex) {
                this._logger.log(Level.WARNING, "XML security error: {0}", ex.getMessage());
            }
        }

        return false;
    }

    private boolean isDigested(NodeList nodeList, Element signatureElement)
            throws XMLSignatureException, XMLSecurityException {
        NodeList referenceNodeList = signatureElement.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#",
                "Reference");
        Document document = nodeList.item(0).getOwnerDocument();
        Manifest manifest = new Manifest(document);
        VerifyReference[] references = new VerifyReference[referenceNodeList.getLength()];
        for (int referenceIdx = 0; referenceIdx < referenceNodeList.getLength(); referenceIdx++) {
            Element referenceElement = (Element) referenceNodeList.item(referenceIdx);
            VerifyReference reference = new VerifyReference(referenceElement, manifest);
            reference.init();
            references[referenceIdx] = reference;
        }
        return isDigested(nodeList, references);
    }

    private boolean isDigested(NodeList nodes, VerifyReference[] references) {
        for (int idx = 0; idx < nodes.getLength(); idx++) {
            Node node = nodes.item(idx);
            //this._logger.log(Level.FINE, "node name: {0}", node.getLocalName());
            boolean changed = false;
            if (node.getNodeType() == Node.TEXT_NODE) {
                String originalTextValue = node.getNodeValue();
                String changedTextValue = originalTextValue + "foobar";
                node.setNodeValue(changedTextValue);
                changed = false; // need to have impact anyway
                for (int referenceIdx = 0; referenceIdx < references.length; referenceIdx++) {
                    VerifyReference reference = references[referenceIdx];
                    changed |= reference.hasChanged();
                }
                if (false == changed) {
                    return false;
                }
                node.setNodeValue(originalTextValue);
            } else if (node.getNodeType() == Node.ELEMENT_NODE) {
                Element element = (Element) node;

                NamedNodeMap attributes = element.getAttributes();
                for (int attributeIdx = 0; attributeIdx < attributes.getLength(); attributeIdx++) {
                    Node attributeNode = attributes.item(attributeIdx);
                    String originalAttributeValue = attributeNode.getNodeValue();
                    String changedAttributeValue = originalAttributeValue + "foobar";
                    attributeNode.setNodeValue(changedAttributeValue);
                    for (int referenceIdx = 0; referenceIdx < references.length; referenceIdx++) {
                        VerifyReference reference = references[referenceIdx];
                        changed |= reference.hasChanged();
                    }

                    attributeNode.setNodeValue(originalAttributeValue);
                }
                changed |= isDigested(element.getChildNodes(), references);
            } else if (node.getNodeType() == Node.COMMENT_NODE) {
                // not always digested by the ds:References
            } else {
                throw new RuntimeException("unsupported node type: " + node.getNodeType());
            }
            if (false == changed) {
                return false;
            }
        }
        return true;
    }

    public List<NamedValue> getSAMLAttributes(ConversationID id) {
        List<NamedValue> samlAttributes = new ArrayList<NamedValue>();

        Document document = getSAMLDocument(id);
        if (null == document) {
            return samlAttributes;
        }

        NodeList attributeNodeList = document.getElementsByTagNameNS("urn:oasis:names:tc:SAML:1.0:assertion",
                "Attribute");
        for (int idx = 0; idx < attributeNodeList.getLength(); idx++) {
            Element attributeElement = (Element) attributeNodeList.item(idx);
            String attributeName = attributeElement.getAttribute("AttributeName");
            NodeList attributeValueNodeList = attributeElement
                    .getElementsByTagNameNS("urn:oasis:names:tc:SAML:1.0:assertion", "AttributeValue");
            if (0 == attributeValueNodeList.getLength()) {
                continue;
            }
            Element attributeValueElement = (Element) attributeValueNodeList.item(0);
            String attributeValue = attributeValueElement.getChildNodes().item(0).getNodeValue();
            NamedValue attribute = new NamedValue(attributeName, attributeValue);
            samlAttributes.add(attribute);
        }

        NodeList attribute2NodeList = document.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion",
                "Attribute");
        for (int idx = 0; idx < attribute2NodeList.getLength(); idx++) {
            Element attributeElement = (Element) attribute2NodeList.item(idx);
            String attributeName = attributeElement.getAttribute("Name");
            NodeList attributeValueNodeList = attributeElement
                    .getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion", "AttributeValue");
            if (0 == attributeValueNodeList.getLength()) {
                continue;
            }
            Element attributeValueElement = (Element) attributeValueNodeList.item(0);
            String attributeValue = attributeValueElement.getChildNodes().item(0).getNodeValue();
            NamedValue attribute = new NamedValue(attributeName, attributeValue);
            samlAttributes.add(attribute);
        }

        return samlAttributes;
    }

    public boolean hasValidityIntervalIndication(ConversationID id) {
        Document document = getSAMLDocument(id);
        if (null == document) {
            return false;
        }

        NodeList saml1AssertionNodeList = document.getElementsByTagNameNS("urn:oasis:names:tc:SAML:1.0:assertion",
                "Assertion");
        if (0 != saml1AssertionNodeList.getLength()) {
            Element assertionElement = (Element) saml1AssertionNodeList.item(0);
            NodeList conditionsNodeList = assertionElement
                    .getElementsByTagNameNS("urn:oasis:names:tc:SAML:1.0:assertion", "Conditions");
            if (0 != conditionsNodeList.getLength()) {
                Element conditionsElement = (Element) conditionsNodeList.item(0);
                if (null != conditionsElement.getAttributeNode("NotBefore")
                        && null != conditionsElement.getAttributeNode("NotOnOrAfter")) {
                    return true;
                }
            }
        }

        NodeList saml2AssertionNodeList = document.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion",
                "Assertion");
        if (0 != saml2AssertionNodeList.getLength()) {
            Element assertionElement = (Element) saml2AssertionNodeList.item(0);
            NodeList conditionsNodeList = assertionElement
                    .getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion", "Conditions");
            if (0 != conditionsNodeList.getLength()) {
                Element conditionsElement = (Element) conditionsNodeList.item(0);
                if (null != conditionsElement.getAttributeNode("NotBefore")
                        && null != conditionsElement.getAttributeNode("NotOnOrAfter")) {
                    return true;
                }
            }
        }

        NodeList saml2AuthnRequestNodeList = document.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:protocol",
                "AuthnRequest");
        if (0 != saml2AuthnRequestNodeList.getLength()) {
            Element authnRequestElement = (Element) saml2AuthnRequestNodeList.item(0);
            NodeList conditionsNodeList = authnRequestElement
                    .getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion", "Conditions");
            if (0 != conditionsNodeList.getLength()) {
                Element conditionsElement = (Element) conditionsNodeList.item(0);
                if (null != conditionsElement.getAttributeNode("NotBefore")
                        && null != conditionsElement.getAttributeNode("NotOnOrAfter")) {
                    return true;
                }
            }
        }

        return false;
    }

    public boolean hasEncryptedAttributes(ConversationID id) {
        Document document = getSAMLDocument(id);
        if (null == document) {
            return false;
        }

        NodeList encryptedAttributeNodeList = document
                .getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion", "EncryptedAttribute");
        if (0 != encryptedAttributeNodeList.getLength()) {
            return true;
        }
        return false;
    }

    public List getDecryptedAttributes(ConversationID id, String hexKey) throws Exception {
        List samlAttributes = new ArrayList();

        /*
         * We create a new DOM tree as XMLCipher will change the tree.
         */
        String encodedSamlMessage = getEncodedSAMLMessage(id);
        String decodedSamlMessage = getDecodedSAMLMessage(encodedSamlMessage, id);
        ByteArrayInputStream inputStream = new ByteArrayInputStream(decodedSamlMessage.getBytes());
        Document document = this.builder.parse(inputStream);

        byte[] keyBytes = Hex.decode(hexKey);
        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
        XMLCipher xmlCipher = XMLCipher.getInstance(XMLCipher.AES_128);
        xmlCipher.init(XMLCipher.DECRYPT_MODE, secretKeySpec);

        NodeList encryptedAttributeNodeList = document
                .getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion", "EncryptedAttribute");
        for (int encryptedAttributeIdx = 0; encryptedAttributeIdx < encryptedAttributeNodeList
                .getLength(); encryptedAttributeIdx++) {
            Element encryptedAttributeElement = (Element) encryptedAttributeNodeList.item(encryptedAttributeIdx);
            NodeList encryptedDataNodeList = encryptedAttributeElement
                    .getElementsByTagNameNS("http://www.w3.org/2001/04/xmlenc#", "EncryptedData");
            if (1 != encryptedDataNodeList.getLength()) {
                continue;
            }
            Element encryptedDataElement = (Element) encryptedDataNodeList.item(0);
            xmlCipher.doFinal(document, encryptedDataElement);
            NodeList attributeNodeList = encryptedAttributeElement
                    .getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion", "Attribute");
            if (1 != attributeNodeList.getLength()) {
                continue;
            }
            Element attributeElement = (Element) attributeNodeList.item(0);
            String attributeName = attributeElement.getAttribute("Name");
            NodeList attributeValueNodeList = attributeElement
                    .getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion", "AttributeValue");
            if (0 == attributeValueNodeList.getLength()) {
                continue;
            }
            Element attributeValueElement = (Element) attributeValueNodeList.item(0);
            String attributeValue = attributeValueElement.getChildNodes().item(0).getNodeValue();
            NamedValue attribute = new NamedValue(attributeName, attributeValue);
            samlAttributes.add(attribute);
        }

        return samlAttributes;
    }

    public byte[] getEncryptedAssertion(ConversationID id) {
        Document samlDocument = getSAMLDocument(id);
        NodeList encryptedAssertionNodeList = samlDocument
                .getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion", "EncryptedAssertion");
        if (encryptedAssertionNodeList.getLength() == 0) {
            return null;
        }
        Element encryptedAssertionElement = (Element) encryptedAssertionNodeList.item(0);
        try {
            return toString(encryptedAssertionElement).getBytes();
        } catch (TransformerException ex) {
            return null;
        }
    }

    private String toString(Node node) throws TransformerConfigurationException, TransformerException {
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        StringWriter stringWriter = new StringWriter();
        transformer.transform(new DOMSource(node), new StreamResult(stringWriter));
        return stringWriter.toString();
    }

    public byte[] getDecryptedAssertion(ConversationID id, PrivateKey privateKey)
            throws ParserConfigurationException, SAXException, IOException, TransformerException,
            XMLEncryptionException, Exception {
        byte[] encryptedAssertion = getEncryptedAssertion(id);
        if (null == encryptedAssertion) {
            return null;
        }
        if (null == privateKey) {
            return "<error>null private key</error>".getBytes();
        }
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setNamespaceAware(true);
        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
        Document document = documentBuilder.parse(new ByteArrayInputStream(encryptedAssertion));

        Element encryptedDataElement = (Element) document
                .getElementsByTagNameNS("http://www.w3.org/2001/04/xmlenc#", "EncryptedData").item(0);
        if (null == encryptedDataElement) {
            return "missing encrypted data element".getBytes();
        }
        XMLCipher xmlCipher = XMLCipher.getInstance(XMLCipher.AES_128);
        xmlCipher.init(XMLCipher.DECRYPT_MODE, null);
        xmlCipher.setKEK(privateKey);
        document = xmlCipher.doFinal(document, encryptedDataElement);

        return toString(document).getBytes();
    }
}