keycloak-aplcache

KEYCLOAK-3864 Add support for SAML2 <Extensions> element

11/1/2016 10:39:14 AM

Changes

saml-core/pom.xml 12(+12 -0)

Details

saml-core/pom.xml 12(+12 -0)

diff --git a/saml-core/pom.xml b/saml-core/pom.xml
index 8483240..8c08b69 100755
--- a/saml-core/pom.xml
+++ b/saml-core/pom.xml
@@ -53,6 +53,18 @@
             <groupId>org.apache.santuario</groupId>
             <artifactId>xmlsec</artifactId>
         </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-core</artifactId>
+            <version>1.3</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     <build>
         <resources>
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/DefaultPicketLinkLogger.java b/saml-core/src/main/java/org/keycloak/saml/common/DefaultPicketLinkLogger.java
index 76a72ae..2f58911 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/DefaultPicketLinkLogger.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/DefaultPicketLinkLogger.java
@@ -450,6 +450,11 @@ public class DefaultPicketLinkLogger implements PicketLinkLogger {
         return new RuntimeException(ErrorCodes.EXPECTED_TAG + tag + ">.  Found <" + foundElementTag + ">");
     }
 
+    @Override
+    public RuntimeException parserExpectedNamespace(String ns, String foundElementNs) {
+        return new RuntimeException(ErrorCodes.EXPECTED_NAMESPACE + ns + ">.  Found <" + foundElementNs + ">");
+    }
+
     /*
      *(non-Javadoc)
      *
@@ -2378,4 +2383,10 @@ public class DefaultPicketLinkLogger implements PicketLinkLogger {
         return new ProcessingException("Wrong audience [" + serviceURL + "].");
     }
 
+    @Override
+    public ProcessingException samlExtensionUnknownChild(Class<?> clazz) {
+        return new ProcessingException("Unknown child type specified for extension: " 
+          + (clazz == null ? "<null>" : clazz.getSimpleName())
+          + ".");
+    }
 }
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/ErrorCodes.java b/saml-core/src/main/java/org/keycloak/saml/common/ErrorCodes.java
index 09f4301..37a7755 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/ErrorCodes.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/ErrorCodes.java
@@ -48,6 +48,8 @@ public interface ErrorCodes {
 
     String EXPECTED_TAG = "PL00066: Parser : Expected start tag:";
 
+    String EXPECTED_NAMESPACE = "PL00107: Parser : Expected start element namespace:";
+
     String EXPECTED_TEXT_VALUE = "PL00071: Parser: Expected text value:";
 
     String EXPECTED_END_TAG = "PL00066: Parser : Expected end tag:";
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/PicketLinkLogger.java b/saml-core/src/main/java/org/keycloak/saml/common/PicketLinkLogger.java
index 7ac6a09..91f2f54 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/PicketLinkLogger.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/PicketLinkLogger.java
@@ -297,6 +297,14 @@ public interface PicketLinkLogger {
     RuntimeException parserExpectedTag(String tag, String foundElementTag);
 
     /**
+     * @param ns
+     * @param foundElementNs
+     *
+     * @return
+     */
+    RuntimeException parserExpectedNamespace(String ns, String foundElementNs);
+
+    /**
      * @param elementName
      *
      * @return
@@ -1219,4 +1227,6 @@ public interface PicketLinkLogger {
     RuntimeException parserFeatureNotSupported(String feature);
 
     ProcessingException samlAssertionWrongAudience(String serviceURL);
+
+    ProcessingException samlExtensionUnknownChild(Class<?> clazz);
 }
\ No newline at end of file
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResolveParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResolveParser.java
index ca0316b..06d6042 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResolveParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResolveParser.java
@@ -58,6 +58,8 @@ public class SAMLArtifactResolveParser extends SAMLRequestAbstractParser impleme
                 continue;
             } else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
                 continue;
+            } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+                continue;
             } else
                 throw new RuntimeException(ErrorCodes.UNKNOWN_START_ELEMENT + elementName + "::location="
                         + startElement.getLocation());
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResponseParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResponseParser.java
index 5f98403..9d3686b 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResponseParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResponseParser.java
@@ -68,6 +68,9 @@ public class SAMLArtifactResponseParser extends SAMLStatusResponseTypeParser imp
             } else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
                 Element sig = StaxParserUtil.getDOMElement(xmlEventReader);
                 response.setSignature(sig);
+            } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+                SAMLExtensionsParser extensionsParser = new SAMLExtensionsParser();
+                response.setExtensions(extensionsParser.parse(xmlEventReader));
             } else if (JBossSAMLConstants.AUTHN_REQUEST.get().equals(elementName)) {
                 SAMLAuthNRequestParser authnParser = new SAMLAuthNRequestParser();
                 AuthnRequestType authn = (AuthnRequestType) authnParser.parse(xmlEventReader);
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeQueryParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeQueryParser.java
index 139e4e0..6102e9e 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeQueryParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeQueryParser.java
@@ -60,6 +60,8 @@ public class SAMLAttributeQueryParser extends SAMLRequestAbstractParser implemen
                 continue;
             } else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
                 continue;
+            } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+                continue;
             } else
                 throw new RuntimeException(ErrorCodes.UNKNOWN_START_ELEMENT + elementName + "::location="
                         + startElement.getLocation());
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAuthNRequestParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAuthNRequestParser.java
index 5a15b2c..f1d3349 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAuthNRequestParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAuthNRequestParser.java
@@ -76,6 +76,8 @@ public class SAMLAuthNRequestParser extends SAMLRequestAbstractParser implements
                 continue;
             } else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
                 continue;
+            } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+                continue;
             } else
                 throw new RuntimeException(ErrorCodes.UNKNOWN_START_ELEMENT + elementName + "::location="
                         + startElement.getLocation());
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLExtensionsParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLExtensionsParser.java
new file mode 100644
index 0000000..5f7ebbb
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLExtensionsParser.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.saml.processing.core.parsers.saml;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
+import org.keycloak.saml.common.PicketLinkLogger;
+import org.keycloak.saml.common.PicketLinkLoggerFactory;
+import org.keycloak.saml.common.constants.JBossSAMLConstants;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.parsers.ParserNamespaceSupport;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+/**
+ * Parses &lt;samlp:Extensions&gt; SAML2 element into series of DOM nodes.
+ *
+ * @author hmlnarik
+ */
+public class SAMLExtensionsParser implements ParserNamespaceSupport {
+
+    private static final String EXTENSIONS = JBossSAMLConstants.EXTENSIONS.get();
+
+    private static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
+
+    @Override
+    public ExtensionsType parse(XMLEventReader xmlEventReader) throws ParsingException {
+        // Get the startelement
+        StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
+        StaxParserUtil.validate(startElement, EXTENSIONS);
+
+        ExtensionsType extensions = new ExtensionsType();
+
+        while (xmlEventReader.hasNext()) {
+            XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
+            if (xmlEvent instanceof EndElement) {
+                EndElement endElement = (EndElement) xmlEvent;
+                if (StaxParserUtil.matches(endElement, EXTENSIONS)) {
+                    endElement = StaxParserUtil.getNextEndElement(xmlEventReader);
+                    break;
+                } else
+                    throw logger.parserUnknownEndElement(StaxParserUtil.getEndElementName(endElement));
+            }
+
+            startElement = StaxParserUtil.peekNextStartElement(xmlEventReader);
+            if (startElement == null)
+                break;
+
+            extensions.addExtension(StaxParserUtil.getDOMElement(xmlEventReader));
+        }
+
+        return extensions;
+    }
+
+    @Override
+    public boolean supports(QName qname) {
+        String nsURI = qname.getNamespaceURI();
+        String localPart = qname.getLocalPart();
+
+        return nsURI.equals(JBossSAMLURIConstants.PROTOCOL_NSURI.get())
+                && localPart.equals(JBossSAMLConstants.EXTENSIONS.get());
+    }
+
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLResponseParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLResponseParser.java
index a2691c2..92eaf90 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLResponseParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLResponseParser.java
@@ -71,6 +71,9 @@ public class SAMLResponseParser extends SAMLStatusResponseTypeParser implements 
             } else if (JBossSAMLConstants.ASSERTION.get().equals(elementName)) {
                 SAMLAssertionParser assertionParser = new SAMLAssertionParser();
                 response.addAssertion(new RTChoiceType((AssertionType) assertionParser.parse(xmlEventReader)));
+            } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+                SAMLExtensionsParser extensionsParser = new SAMLExtensionsParser();
+                response.setExtensions(extensionsParser.parse(xmlEventReader));
             } else if (JBossSAMLConstants.STATUS.get().equals(elementName)) {
                 response.setStatus(parseStatus(xmlEventReader));
             } else if (JBossSAMLConstants.ENCRYPTED_ASSERTION.get().equals(elementName)) {
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloRequestParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloRequestParser.java
index f604cf5..22ed383 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloRequestParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloRequestParser.java
@@ -74,6 +74,8 @@ public class SAMLSloRequestParser extends SAMLRequestAbstractParser implements P
                 continue;
             } else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
                 continue;
+            } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+                continue;
             } else
                 throw logger.parserUnknownTag(elementName, startElement.getLocation());
         }
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloResponseParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloResponseParser.java
index 167a3c5..c0f473b 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloResponseParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloResponseParser.java
@@ -60,6 +60,9 @@ public class SAMLSloResponseParser extends SAMLStatusResponseTypeParser implemen
             } else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
                 startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
                 StaxParserUtil.bypassElementBlock(xmlEventReader, JBossSAMLConstants.SIGNATURE.get());
+            } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+                SAMLExtensionsParser extensionsParser = new SAMLExtensionsParser();
+                response.setExtensions(extensionsParser.parse(xmlEventReader));
             } else if (JBossSAMLConstants.STATUS.get().equals(elementName)) {
                 response.setStatus(parseStatus(xmlEventReader));
             }
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java
index 4c041d1..068c91a 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java
@@ -43,8 +43,12 @@ import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
+import org.keycloak.saml.SamlProtocolExtensionsAwareBuilder;
 
 import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.ASSERTION_NSURI;
+import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.PROTOCOL_NSURI;
+import org.w3c.dom.Node;
 
 /**
  * Base Class for the Stax writers for SAML
@@ -244,6 +248,28 @@ public class BaseWriter {
         StaxUtil.flush(writer);
     }
 
+    public void write(ExtensionsType extensions) throws ProcessingException {
+        if (extensions.getAny().isEmpty()) {
+            return;
+        }
+
+        StaxUtil.writeStartElement(writer, PROTOCOL_PREFIX, JBossSAMLConstants.EXTENSIONS.get(), PROTOCOL_NSURI.get());
+
+        for (Object o : extensions.getAny()) {
+            if (o instanceof Node) {
+                StaxUtil.writeDOMNode(writer, (Node) o);
+            } else if (o instanceof SamlProtocolExtensionsAwareBuilder.NodeGenerator) {
+                SamlProtocolExtensionsAwareBuilder.NodeGenerator ng = (SamlProtocolExtensionsAwareBuilder.NodeGenerator) o;
+                ng.write(writer);
+            } else {
+                throw logger.samlExtensionUnknownChild(o == null ? null : o.getClass());
+            }
+        }
+
+        StaxUtil.writeEndElement(writer);
+        StaxUtil.flush(writer);
+    }
+
     private void write(SubjectConfirmationType subjectConfirmationType) throws ProcessingException {
         StaxUtil.writeStartElement(writer, ASSERTION_PREFIX, JBossSAMLConstants.SUBJECT_CONFIRMATION.get(),
                 ASSERTION_NSURI.get());
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLRequestWriter.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLRequestWriter.java
index 9f99780..8c115f5 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLRequestWriter.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLRequestWriter.java
@@ -36,6 +36,7 @@ import javax.xml.namespace.QName;
 import javax.xml.stream.XMLStreamWriter;
 import java.net.URI;
 import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
 
 import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.ASSERTION_NSURI;
 import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.PROTOCOL_NSURI;
@@ -122,6 +123,11 @@ public class SAMLRequestWriter extends BaseWriter {
             StaxUtil.writeDOMElement(writer, sig);
         }
 
+        ExtensionsType extensions = request.getExtensions();
+        if (extensions != null && ! extensions.getAny().isEmpty()) {
+            write(extensions);
+        }
+
         NameIDPolicyType nameIDPolicy = request.getNameIDPolicy();
         if (nameIDPolicy != null) {
             write(nameIDPolicy);
@@ -171,6 +177,11 @@ public class SAMLRequestWriter extends BaseWriter {
             StaxUtil.writeDOMElement(writer, signature);
         }
 
+        ExtensionsType extensions = logOutRequest.getExtensions();
+        if (extensions != null && ! extensions.getAny().isEmpty()) {
+            write(extensions);
+        }
+
         NameIDType nameID = logOutRequest.getNameID();
         if (nameID != null) {
             write(nameID, new QName(ASSERTION_NSURI.get(), JBossSAMLConstants.NAMEID.get(), ASSERTION_PREFIX));
@@ -278,6 +289,11 @@ public class SAMLRequestWriter extends BaseWriter {
         if (sig != null) {
             StaxUtil.writeDOMElement(writer, sig);
         }
+        ExtensionsType extensions = request.getExtensions();
+        if (extensions != null && ! extensions.getAny().isEmpty()) {
+            write(extensions);
+        }
+
         String artifact = request.getArtifact();
         if (StringUtil.isNotNull(artifact)) {
             StaxUtil.writeStartElement(writer, PROTOCOL_PREFIX, JBossSAMLConstants.ARTIFACT.get(), PROTOCOL_NSURI.get());
@@ -315,6 +331,10 @@ public class SAMLRequestWriter extends BaseWriter {
         if (sig != null) {
             StaxUtil.writeDOMElement(writer, sig);
         }
+        ExtensionsType extensions = request.getExtensions();
+        if (extensions != null && ! extensions.getAny().isEmpty()) {
+            write(extensions);
+        }
         SubjectType subject = request.getSubject();
         if (subject != null) {
             write(subject);
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLResponseWriter.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLResponseWriter.java
index 07fae2a..9327a73 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLResponseWriter.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLResponseWriter.java
@@ -37,6 +37,7 @@ import javax.xml.namespace.QName;
 import javax.xml.stream.XMLStreamWriter;
 import java.net.URI;
 import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
 
 /**
  * Write a SAML Response to stream
@@ -78,6 +79,10 @@ public class SAMLResponseWriter extends BaseWriter {
         if (sig != null) {
             StaxUtil.writeDOMElement(writer, sig);
         }
+        ExtensionsType extensions = response.getExtensions();
+        if (extensions != null && extensions.getAny() != null && ! extensions.getAny().isEmpty()) {
+            write(extensions);
+        }
 
         StatusType status = response.getStatus();
         write(status);
@@ -119,6 +124,10 @@ public class SAMLResponseWriter extends BaseWriter {
         if (sig != null) {
             StaxUtil.writeDOMElement(writer, sig);
         }
+        ExtensionsType extensions = response.getExtensions();
+        if (extensions != null && extensions.getAny() != null && ! extensions.getAny().isEmpty()) {
+            write(extensions);
+        }
 
         StatusType status = response.getStatus();
         if (status != null) {
@@ -163,6 +172,15 @@ public class SAMLResponseWriter extends BaseWriter {
         NameIDType issuer = response.getIssuer();
         write(issuer, new QName(JBossSAMLURIConstants.ASSERTION_NSURI.get(), JBossSAMLConstants.ISSUER.get(), ASSERTION_PREFIX));
 
+        Element sig = response.getSignature();
+        if (sig != null) {
+            StaxUtil.writeDOMElement(writer, sig);
+        }
+        ExtensionsType extensions = response.getExtensions();
+        if (extensions != null && extensions.getAny() != null && ! extensions.getAny().isEmpty()) {
+            write(extensions);
+        }
+
         StatusType status = response.getStatus();
         write(status);
 
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java
index 302237e..ec4fc28 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java
@@ -25,15 +25,19 @@ import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
 import org.w3c.dom.Document;
 
 import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
 
 /**
  * @author pedroigor
  */
-public class SAML2AuthnRequestBuilder {
+public class SAML2AuthnRequestBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2AuthnRequestBuilder> {
 
     private final AuthnRequestType authnRequestType;
     protected String destination;
     protected String issuer;
+    protected final List<NodeGenerator> extensions = new LinkedList<>();
 
     public SAML2AuthnRequestBuilder destination(String destination) {
         this.destination = destination;
@@ -45,6 +49,12 @@ public class SAML2AuthnRequestBuilder {
         return this;
     }
 
+    @Override
+    public SAML2AuthnRequestBuilder addExtension(NodeGenerator extension) {
+        this.extensions.add(extension);
+        return this;
+    }
+
     public SAML2AuthnRequestBuilder() {
         try {
             this.authnRequestType = new AuthnRequestType(IDGenerator.create("ID_"), XMLTimeUtil.getIssueInstant());
@@ -90,6 +100,14 @@ public class SAML2AuthnRequestBuilder {
 
             authnRequestType.setDestination(URI.create(this.destination));
 
+            if (! this.extensions.isEmpty()) {
+                ExtensionsType extensionsType = new ExtensionsType();
+                for (NodeGenerator extension : this.extensions) {
+                    extensionsType.addExtension(extension);
+                }
+                authnRequestType.setExtensions(extensionsType);
+            }
+
             return new SAML2Request().convert(authnRequestType);
         } catch (Exception e) {
             throw new RuntimeException("Could not convert " + authnRequestType + " to a document.", e);
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java
index 99d1c1f..6da6799 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java
@@ -17,7 +17,10 @@
 
 package org.keycloak.saml;
 
+import java.util.LinkedList;
+import java.util.List;
 import org.keycloak.dom.saml.v2.assertion.NameIDType;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
 import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
 import org.keycloak.saml.common.exceptions.ConfigurationException;
 import org.keycloak.saml.common.exceptions.ParsingException;
@@ -32,11 +35,12 @@ import org.w3c.dom.Document;
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class SAML2ErrorResponseBuilder {
+public class SAML2ErrorResponseBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2ErrorResponseBuilder> {
 
     protected String status;
     protected String destination;
     protected String issuer;
+    protected final List<NodeGenerator> extensions = new LinkedList<>();
 
     public SAML2ErrorResponseBuilder status(String status) {
         this.status = status;
@@ -53,6 +57,11 @@ public class SAML2ErrorResponseBuilder {
         return this;
     }
 
+    @Override
+    public SAML2ErrorResponseBuilder addExtension(NodeGenerator extension) {
+        this.extensions.add(extension);
+        return this;
+    }
 
     public Document buildDocument() throws ProcessingException {
 
@@ -66,6 +75,14 @@ public class SAML2ErrorResponseBuilder {
             statusResponse.setIssuer(issuer);
             statusResponse.setDestination(destination);
 
+            if (! this.extensions.isEmpty()) {
+                ExtensionsType extensionsType = new ExtensionsType();
+                for (NodeGenerator extension : this.extensions) {
+                    extensionsType.addExtension(extension);
+                }
+                statusResponse.setExtensions(extensionsType);
+            }
+
             SAML2Response saml2Response = new SAML2Response();
             return saml2Response.convert(statusResponse);
         } catch (ConfigurationException e) {
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2LoginResponseBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2LoginResponseBuilder.java
index 2386edb..17dafc7 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2LoginResponseBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2LoginResponseBuilder.java
@@ -39,6 +39,9 @@ import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
 import org.w3c.dom.Document;
 
 import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
 
 import static org.keycloak.saml.common.util.StringUtil.isNotNull;
 
@@ -49,7 +52,7 @@ import static org.keycloak.saml.common.util.StringUtil.isNotNull;
  *
  * @author bburke@redhat.com
 */
-public class SAML2LoginResponseBuilder {
+public class SAML2LoginResponseBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2LoginResponseBuilder> {
     protected static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
 
     protected String destination;
@@ -64,6 +67,7 @@ public class SAML2LoginResponseBuilder {
     protected String authMethod;
     protected String requestIssuer;
     protected String sessionIndex;
+    protected final List<NodeGenerator> extensions = new LinkedList<>();
 
 
     public SAML2LoginResponseBuilder sessionIndex(String sessionIndex) {
@@ -136,6 +140,12 @@ public class SAML2LoginResponseBuilder {
         return this;
     }
 
+    @Override
+    public SAML2LoginResponseBuilder addExtension(NodeGenerator extension) {
+        this.extensions.add(extension);
+        return this;
+    }
+
     public Document buildDocument(ResponseType responseType) throws ConfigurationException, ProcessingException {
         Document samlResponseDocument = null;
 
@@ -207,6 +217,14 @@ public class SAML2LoginResponseBuilder {
             assertion.addStatement(authnStatement);
         }
 
+        if (! this.extensions.isEmpty()) {
+            ExtensionsType extensionsType = new ExtensionsType();
+            for (NodeGenerator extension : this.extensions) {
+                extensionsType.addExtension(extension);
+            }
+            responseType.setExtensions(extensionsType);
+        }
+
         return responseType;
     }
 
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java
index 99b1cf8..d0e81ba 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java
@@ -27,18 +27,22 @@ import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
 import org.w3c.dom.Document;
 
 import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class SAML2LogoutRequestBuilder {
+public class SAML2LogoutRequestBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2LogoutRequestBuilder> {
     protected String userPrincipal;
     protected String userPrincipalFormat;
     protected String sessionIndex;
     protected long assertionExpiration;
     protected String destination;
     protected String issuer;
+    protected final List<NodeGenerator> extensions = new LinkedList<>();
 
     public SAML2LogoutRequestBuilder destination(String destination) {
         this.destination = destination;
@@ -50,6 +54,12 @@ public class SAML2LogoutRequestBuilder {
         return this;
     }
 
+    @Override
+    public SAML2LogoutRequestBuilder addExtension(NodeGenerator extension) {
+        this.extensions.add(extension);
+        return this;
+    }
+
     /**
      * Length of time in seconds the assertion is valid for
      * See SAML core specification 2.5.1.2 NotOnOrAfter
@@ -99,6 +109,15 @@ public class SAML2LogoutRequestBuilder {
 
         if (assertionExpiration > 0) lort.setNotOnOrAfter(XMLTimeUtil.add(lort.getIssueInstant(), assertionExpiration * 1000));
         lort.setDestination(URI.create(destination));
+
+        if (! this.extensions.isEmpty()) {
+            ExtensionsType extensionsType = new ExtensionsType();
+            for (NodeGenerator extension : this.extensions) {
+                extensionsType.addExtension(extension);
+            }
+            lort.setExtensions(extensionsType);
+        }
+
         return lort;
     }
 }
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutResponseBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutResponseBuilder.java
index c00a4d4..8050e81 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutResponseBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutResponseBuilder.java
@@ -31,16 +31,20 @@ import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
 import org.w3c.dom.Document;
 
 import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class SAML2LogoutResponseBuilder {
+public class SAML2LogoutResponseBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2LogoutResponseBuilder> {
 
     protected String logoutRequestID;
     protected String destination;
     protected String issuer;
+    protected final List<NodeGenerator> extensions = new LinkedList<>();
 
     public SAML2LogoutResponseBuilder logoutRequestID(String logoutRequestID) {
         this.logoutRequestID = logoutRequestID;
@@ -57,6 +61,11 @@ public class SAML2LogoutResponseBuilder {
         return this;
     }
 
+    @Override
+    public SAML2LogoutResponseBuilder addExtension(NodeGenerator extension) {
+        this.extensions.add(extension);
+        return this;
+    }
 
     public Document buildDocument() throws ProcessingException {
         Document samlResponse = null;
@@ -77,6 +86,14 @@ public class SAML2LogoutResponseBuilder {
             statusResponse.setIssuer(issuer);
             statusResponse.setDestination(destination);
 
+            if (! this.extensions.isEmpty()) {
+                ExtensionsType extensionsType = new ExtensionsType();
+                for (NodeGenerator extension : this.extensions) {
+                    extensionsType.addExtension(extension);
+                }
+                statusResponse.setExtensions(extensionsType);
+            }
+
             SAML2Response saml2Response = new SAML2Response();
             samlResponse = saml2Response.convert(statusResponse);
         } catch (ConfigurationException e) {
diff --git a/saml-core/src/main/java/org/keycloak/saml/SamlProtocolExtensionsAwareBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SamlProtocolExtensionsAwareBuilder.java
new file mode 100644
index 0000000..2192df6
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/SamlProtocolExtensionsAwareBuilder.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.saml;
+
+import javax.xml.stream.XMLStreamWriter;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+
+/**
+ * Implementations of this interface are builders that can register &lt;samlp:Extensions&gt;
+ * content providers.
+ *
+ * @author hmlnarik
+ */
+public interface SamlProtocolExtensionsAwareBuilder<T> {
+
+    public interface NodeGenerator {
+        /**
+         * Generate contents of the &lt;samlp:Extensions&gt; tag. When this method is invoked,
+         * the writer has already emitted the &lt;samlp:Extensions&gt; start tag.
+         *
+         * @param writer Writer to use for producing XML output
+         * @throws ProcessingException If any exception fails
+         */
+        void write(XMLStreamWriter writer) throws ProcessingException;
+    }
+
+    /**
+     * Adds a given node subtree as a SAML protocol extension into the SAML protocol message.
+     *
+     * @param extension
+     * @return
+     */
+    T addExtension(NodeGenerator extension);
+}
diff --git a/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java b/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java
new file mode 100644
index 0000000..ad150e9
--- /dev/null
+++ b/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.saml.processing.core.parsers.saml;
+
+import java.io.InputStream;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.hamcrest.CoreMatchers.*;
+import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
+import org.keycloak.dom.saml.v2.protocol.ResponseType;
+import org.w3c.dom.Element;
+
+/**
+ * Test class for SAML parser.
+ *
+ * TODO: Add further tests.
+ *
+ * @author hmlnarik
+ */
+public class SAMLParserTest {
+
+    @Test
+    public void testSaml20EncryptedAssertionsSignedReceivedWithRedirectBinding() throws Exception {
+        InputStream st = SAMLParserTest.class.getResourceAsStream("saml20-encrypted-signed-redirect-response.xml");
+        SAMLParser parser = new SAMLParser();
+
+        Object parsedObject = parser.parse(st);
+        assertThat(parsedObject, instanceOf(ResponseType.class));
+        
+        ResponseType resp = (ResponseType) parsedObject;
+        assertThat(resp.getSignature(), nullValue());
+        assertThat(resp.getConsent(), nullValue());
+        assertThat(resp.getIssuer(), not(nullValue()));
+        assertThat(resp.getIssuer().getValue(), is("http://localhost:8081/auth/realms/saml-demo"));
+
+        assertThat(resp.getExtensions(), not(nullValue()));
+        assertThat(resp.getExtensions().getAny().size(), is(1));
+        assertThat(resp.getExtensions().getAny().get(0), instanceOf(Element.class));
+        Element el = (Element) resp.getExtensions().getAny().get(0);
+        assertThat(el.getLocalName(), is("KeyInfo"));
+        assertThat(el.getNamespaceURI(), is("urn:keycloak:ext:key:1.0"));
+        assertThat(el.hasAttribute("MessageSigningKeyId"), is(true));
+        assertThat(el.getAttribute("MessageSigningKeyId"), is("FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE"));
+
+        assertThat(resp.getAssertions(), not(nullValue()));
+        assertThat(resp.getAssertions().size(), is(1));
+    }
+
+    @Test
+    public void testSaml20EncryptedAssertionsSignedTwoExtensionsReceivedWithRedirectBinding() throws Exception {
+        Element el;
+
+        InputStream st = SAMLParserTest.class.getResourceAsStream("saml20-encrypted-signed-redirect-response-two-extensions.xml");
+        SAMLParser parser = new SAMLParser();
+
+        Object parsedObject = parser.parse(st);
+        assertThat(parsedObject, instanceOf(ResponseType.class));
+
+        ResponseType resp = (ResponseType) parsedObject;
+        assertThat(resp.getSignature(), nullValue());
+        assertThat(resp.getConsent(), nullValue());
+        assertThat(resp.getIssuer(), not(nullValue()));
+        assertThat(resp.getIssuer().getValue(), is("http://localhost:8081/auth/realms/saml-demo"));
+
+        assertThat(resp.getExtensions(), not(nullValue()));
+        assertThat(resp.getExtensions().getAny().size(), is(2));
+        assertThat(resp.getExtensions().getAny().get(0), instanceOf(Element.class));
+        el = (Element) resp.getExtensions().getAny().get(0);
+        assertThat(el.getLocalName(), is("KeyInfo"));
+        assertThat(el.getNamespaceURI(), is("urn:keycloak:ext:key:1.0"));
+        assertThat(el.hasAttribute("MessageSigningKeyId"), is(true));
+        assertThat(el.getAttribute("MessageSigningKeyId"), is("FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE"));
+        assertThat(resp.getExtensions().getAny().get(1), instanceOf(Element.class));
+        el = (Element) resp.getExtensions().getAny().get(1);
+        assertThat(el.getLocalName(), is("ever"));
+        assertThat(el.getNamespaceURI(), is("urn:keycloak:ext:what:1.0"));
+        assertThat(el.hasAttribute("what"), is(true));
+        assertThat(el.getAttribute("what"), is("ever"));
+
+        assertThat(resp.getAssertions(), not(nullValue()));
+        assertThat(resp.getAssertions().size(), is(1));
+    }
+
+    @Test
+    public void testSaml20PostLogoutRequest() throws Exception {
+        InputStream st = SAMLParserTest.class.getResourceAsStream("saml20-signed-logout-request.xml");
+        SAMLParser parser = new SAMLParser();
+
+        Object parsedObject = parser.parse(st);
+        assertThat(parsedObject, instanceOf(LogoutRequestType.class));
+
+    }
+}
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response.xml
new file mode 100644
index 0000000..d8d4c15
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response.xml
@@ -0,0 +1,29 @@
+<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Destination="http://localhost:8080/sales-post-enc/saml" ID="ID_0b43d444-d1a8-44a5-8caf-38e176489e1f" InResponseTo="ID_223d3591-22fb-4b3c-9e38-4719293b2d94" IssueInstant="2016-11-01T13:52:43.054Z" Version="2.0">
+    <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://localhost:8081/auth/realms/saml-demo</saml:Issuer>
+    <samlp:Extensions>
+        <kckey:KeyInfo xmlns:kckey="urn:keycloak:ext:key:1.0" MessageSigningKeyId="FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE"/>
+    </samlp:Extensions>
+    <samlp:Status>
+        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
+    </samlp:Status>
+    <saml:EncryptedAssertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
+        <xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Element">
+            <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+                <xenc:EncryptedKey xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
+                    <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
+                    <xenc:CipherData>
+                        <xenc:CipherValue>
+                            OkvZTx/ifYLef74rY0F9I8lbJaatgSEguo+zwh5JrYWcO09Ib2gtz5+z+67Is2+wk/OzKp154r8qAI5vY9AYvuXCslKL/wbcZ1UILL78F0T/iiUW3VpWy8Wvz5nezBFPRqot8WiFQykByjlBg1Z8XOts+uIdyqBBi/WjYeJGMaQ=
+                        </xenc:CipherValue>
+                    </xenc:CipherData>
+                </xenc:EncryptedKey>
+            </ds:KeyInfo>
+            <xenc:CipherData>
+                <xenc:CipherValue>
+                    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
+                </xenc:CipherValue>
+            </xenc:CipherData>
+        </xenc:EncryptedData>
+    </saml:EncryptedAssertion>
+</samlp:Response>
\ No newline at end of file
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response-two-extensions.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response-two-extensions.xml
new file mode 100644
index 0000000..94a6fdb
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response-two-extensions.xml
@@ -0,0 +1,30 @@
+<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Destination="http://localhost:8080/sales-post-enc/saml" ID="ID_0b43d444-d1a8-44a5-8caf-38e176489e1f" InResponseTo="ID_223d3591-22fb-4b3c-9e38-4719293b2d94" IssueInstant="2016-11-01T13:52:43.054Z" Version="2.0">
+    <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://localhost:8081/auth/realms/saml-demo</saml:Issuer>
+    <samlp:Extensions>
+        <kckey:KeyInfo xmlns:kckey="urn:keycloak:ext:key:1.0" MessageSigningKeyId="FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE"/>
+        <what:ever xmlns:what="urn:keycloak:ext:what:1.0" what="ever"/>
+    </samlp:Extensions>
+    <samlp:Status>
+        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
+    </samlp:Status>
+    <saml:EncryptedAssertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
+        <xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Element">
+            <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+                <xenc:EncryptedKey xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
+                    <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
+                    <xenc:CipherData>
+                        <xenc:CipherValue>
+                            OkvZTx/ifYLef74rY0F9I8lbJaatgSEguo+zwh5JrYWcO09Ib2gtz5+z+67Is2+wk/OzKp154r8qAI5vY9AYvuXCslKL/wbcZ1UILL78F0T/iiUW3VpWy8Wvz5nezBFPRqot8WiFQykByjlBg1Z8XOts+uIdyqBBi/WjYeJGMaQ=
+                        </xenc:CipherValue>
+                    </xenc:CipherData>
+                </xenc:EncryptedKey>
+            </ds:KeyInfo>
+            <xenc:CipherData>
+                <xenc:CipherValue>
+                    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
+                </xenc:CipherValue>
+            </xenc:CipherData>
+        </xenc:EncryptedData>
+    </saml:EncryptedAssertion>
+</samlp:Response>
\ No newline at end of file
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-signed-logout-request.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-signed-logout-request.xml
new file mode 100644
index 0000000..8c4ab20
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-signed-logout-request.xml
@@ -0,0 +1,32 @@
+<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns="urn:oasis:names:tc:SAML:2.0:assertion" Destination="http://localhost:8081/auth/realms/saml-demo/protocol/saml" ID="ID_4790c6a3-4b9f-4c0a-a368-5c0e498544e4" IssueInstant="2016-11-01T14:36:43.194Z" Version="2.0">
+    <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://localhost:8080/sales-post-enc/</saml:Issuer>
+    <dsig:Signature xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
+        <dsig:SignedInfo>
+            <dsig:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+            <dsig:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
+            <dsig:Reference URI="#ID_4790c6a3-4b9f-4c0a-a368-5c0e498544e4">
+                <dsig:Transforms>
+                    <dsig:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
+                    <dsig:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+                </dsig:Transforms>
+                <dsig:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
+                <dsig:DigestValue>zeWNo5eav5tFOOCEJ1YU9eINkPnBSfixzAr8AOC4R4c=</dsig:DigestValue>
+            </dsig:Reference>
+        </dsig:SignedInfo>
+        <dsig:SignatureValue>
+            pyOiS1LsV/XR08zhcN6IqSYuKTDln4otmCvZxCc07ORP1C9jragu8V8rEE09qt/zBcdw7Arb8eLNNC6oCnrnMxuvzRInVTwt7T5K3t0UlzRWOb3HMElhcWFEgDzh6uKw5Cr45A01XNpojtJWCML/qU2Enyyy80FBlCJNcbzyLxE=
+        </dsig:SignatureValue>
+        <dsig:KeyInfo>
+            <dsig:KeyValue>
+                <dsig:RSAKeyValue>
+                    <dsig:Modulus>
+                        2+5MCT5BnVN+IYnKZcH6ev1pjXGi4feE0nOycq/VJ3aeaZMi4G9AxOxCBPupErOC7Kgm/Bw5AdJyw+Q12wSRXfJ9FhqCrLXpb7YOhbVSTJ8De5O8mW35DxAlh/cxe9FXjqPb286wKTUZ3LfGYR+X235UQeCTAPS/Ufi21EXaEik=
+                    </dsig:Modulus>
+                    <dsig:Exponent>AQAB</dsig:Exponent>
+                </dsig:RSAKeyValue>
+            </dsig:KeyValue>
+        </dsig:KeyInfo>
+    </dsig:Signature>
+    <saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">bburke</saml:NameID>
+    <samlp:SessionIndex>a3b2df1c-1095-487b-8b56-f62818c449e3</samlp:SessionIndex>
+</samlp:LogoutRequest>