keycloak-aplcache

KEYCLOAK-4897 SAML Adapter fails to validate signature on

6/19/2017 1:26:17 PM

Details

diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
index f4a89dd..4d77c64 100644
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
@@ -362,26 +362,26 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
 
         if (deployment.getIDP().getSingleSignOnService().validateAssertionSignature()) {
             try {
-                validateSamlSignature(new SAMLDocumentHolder(buildAssertionDocument(responseHolder, assertion)), postBinding, GeneralConstants.SAML_RESPONSE_KEY);
-            } catch (VerificationException e) {
-                log.error("Failed to verify saml assertion signature", e);
+                if (!AssertionUtil.isSignatureValid(getAssertionFromResponse(responseHolder), deployment.getIDP().getSignatureValidationKeyLocator())) {
+                    log.error("Failed to verify saml assertion signature");
 
-                challenge = new AuthChallenge() {
+                    challenge = new AuthChallenge() {
 
-                    @Override
-                    public boolean challenge(HttpFacade exchange) {
-                        SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE, responseType);
-                        exchange.getRequest().setError(error);
-                        exchange.getResponse().sendError(403);
-                        return true;
-                    }
+                        @Override
+                        public boolean challenge(HttpFacade exchange) {
+                            SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE, responseType);
+                            exchange.getRequest().setError(error);
+                            exchange.getResponse().sendError(403);
+                            return true;
+                        }
 
-                    @Override
-                    public int getResponseCode() {
-                        return 403;
-                    }
-                };
-                return AuthOutcome.FAILED;
+                        @Override
+                        public int getResponseCode() {
+                            return 403;
+                        }
+                    };
+                    return AuthOutcome.FAILED;
+                }
             } catch (Exception e) {
                 log.error("Error processing validation of SAML assertion: " + e.getMessage());
                 challenge = new AuthChallenge() {
@@ -497,19 +497,16 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
           && Objects.equals(responseType.getStatus().getStatusCode().getValue().toString(), JBossSAMLURIConstants.STATUS_SUCCESS.get());
     }
 
-    private Document buildAssertionDocument(final SAMLDocumentHolder responseHolder, AssertionType assertion) throws ConfigurationException, ProcessingException {
-        Element encryptedAssertion = org.keycloak.saml.common.util.DocumentUtil.getElement(responseHolder.getSamlDocument(), new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
+    private Element getAssertionFromResponse(final SAMLDocumentHolder responseHolder) throws ConfigurationException, ProcessingException {
+        Element encryptedAssertion = DocumentUtil.getElement(responseHolder.getSamlDocument(), new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
         if (encryptedAssertion != null) {
             // encrypted assertion.
             // We'll need to decrypt it first.
             Document encryptedAssertionDocument = DocumentUtil.createDocument();
             encryptedAssertionDocument.appendChild(encryptedAssertionDocument.importNode(encryptedAssertion, true));
-            Element assertionElement = XMLEncryptionUtil.decryptElementInDocument(encryptedAssertionDocument, deployment.getDecryptionKey());
-            Document assertionDocument = DocumentUtil.createDocument();
-            assertionDocument.appendChild(assertionDocument.importNode(assertionElement, true));
-            return assertionDocument;
+            return XMLEncryptionUtil.decryptElementInDocument(encryptedAssertionDocument, deployment.getDecryptionKey());
         }
-        return AssertionUtil.asDocument(assertion);
+        return DocumentUtil.getElement(responseHolder.getSamlDocument(), new QName(JBossSAMLConstants.ASSERTION.get()));
     }
 
     private String getAttributeValue(Object attrValue) {
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/util/DocumentUtil.java b/saml-core/src/main/java/org/keycloak/saml/common/util/DocumentUtil.java
index b5f2232..f516124 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/util/DocumentUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/util/DocumentUtil.java
@@ -52,6 +52,7 @@ import java.io.InputStream;
 import java.io.Reader;
 import java.io.StringReader;
 import java.io.StringWriter;
+import java.util.Objects;
 
 /**
  * Utility dealing with DOM
@@ -554,4 +555,33 @@ public class DocumentUtil {
 
         return documentBuilderFactory;
     }
+
+    /**
+     * Get a (direct) child {@linkplain Element} from the parent {@linkplain Element}. 
+     *
+     * @param parent parent element
+     * @param targetNamespace namespace URI
+     * @param targetLocalName local name
+     * @return a child element matching the target namespace and localname, where {@linkplain Element#getParentNode()} is the parent input parameter
+     * @return
+     */
+    
+    public static Element getDirectChildElement(Element parent, String targetNamespace, String targetLocalName) {
+        Node child = parent.getFirstChild();
+        
+        while(child != null) {
+            if(child instanceof Element) {
+                Element childElement = (Element)child;
+                
+                String ns = childElement.getNamespaceURI();
+                String localName = childElement.getLocalName();
+                
+                if(Objects.equals(targetNamespace, ns) && Objects.equals(targetLocalName, localName)) {
+                    return childElement;
+                }
+            }
+            child = child.getNextSibling();
+        }
+        return null;
+    }
 }
\ No newline at end of file
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java
index ef3e3bd..404c074 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java
@@ -210,9 +210,9 @@ public class SAML2Signature {
      *
      * @param document SAML document to have its ID attribute configured.
      */
-    private void configureIdAttribute(Document document) {
+    public static void configureIdAttribute(Document document) {
         // Estabilish the IDness of the ID attribute.
-        document.getDocumentElement().setIdAttribute(ID_ATTRIBUTE_NAME, true);
+        configureIdAttribute(document.getDocumentElement());
 
         NodeList nodes = document.getElementsByTagNameNS(JBossSAMLURIConstants.ASSERTION_NSURI.get(),
                 JBossSAMLConstants.ASSERTION.get());
@@ -220,8 +220,13 @@ public class SAML2Signature {
         for (int i = 0; i < nodes.getLength(); i++) {
             Node n = nodes.item(i);
             if (n instanceof Element) {
-                ((Element) n).setIdAttribute(ID_ATTRIBUTE_NAME, true);
+                configureIdAttribute((Element) n);
             }
         }
     }
+
+    public static void configureIdAttribute(Element element) {
+        element.setIdAttribute(JBossSAMLConstants.ID.get(), true);
+    }
+
 }
\ No newline at end of file
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
index 714ee3f..244fb7d 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
@@ -32,6 +32,7 @@ import org.keycloak.dom.saml.v2.assertion.StatementAbstractType;
 import org.keycloak.dom.saml.v2.assertion.SubjectType;
 import org.keycloak.dom.saml.v2.assertion.SubjectType.STSubType;
 import org.keycloak.dom.saml.v2.protocol.ResponseType;
+import org.keycloak.rotation.KeyLocator;
 import org.keycloak.saml.common.ErrorCodes;
 import org.keycloak.saml.common.PicketLinkLogger;
 import org.keycloak.saml.common.PicketLinkLoggerFactory;
@@ -48,11 +49,12 @@ import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
 import org.keycloak.saml.processing.core.saml.v2.writers.SAMLAssertionWriter;
 import org.keycloak.saml.processing.core.util.JAXPValidationUtil;
 import org.keycloak.saml.processing.core.util.XMLEncryptionUtil;
-
+import org.keycloak.saml.processing.core.util.XMLSignatureUtil;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 
+import javax.xml.crypto.dsig.XMLSignature;
 import javax.xml.datatype.XMLGregorianCalendar;
 import javax.xml.namespace.QName;
 import java.io.ByteArrayInputStream;
@@ -266,26 +268,56 @@ public class AssertionUtil {
     }
 
     /**
-     * Given an assertion element, validate the signature
+     * Given an {@linkplain Element}, validate the Signature direct child element
      *
-     * @param assertionElement
+     * @param element parent {@linkplain Element}
      * @param publicKey the {@link PublicKey}
      *
-     * @return
+     * @return true if signature is present and valid
      */
-    public static boolean isSignatureValid(Element assertionElement, PublicKey publicKey) {
-        try {
-            Document doc = DocumentUtil.createDocument();
-            Node n = doc.importNode(assertionElement, true);
-            doc.appendChild(n);
+    public static boolean isSignatureValid(Element element, PublicKey publicKey) {
+        return isSignatureValid(element, new HardcodedKeyLocator(publicKey));
+    }
 
-            return new SAML2Signature().validate(doc, new HardcodedKeyLocator(publicKey));
+    /**
+     * Given an {@linkplain Element}, validate the Signature direct child element
+     *
+     * @param element parent {@linkplain Element}
+     * @param keyLocator the {@link KeyLocator}
+     *
+     * @return true if signature is present and valid
+     */
+    
+    public static boolean isSignatureValid(Element element, KeyLocator keyLocator) {
+        try {
+            SAML2Signature.configureIdAttribute(element);
+            
+            Element signature = getSignature(element);
+            if(signature != null) {
+                return XMLSignatureUtil.validateSingleNode(signature, keyLocator);
+            }
         } catch (Exception e) {
             logger.signatureAssertionValidationError(e);
         }
         return false;
     }
+    
+    /**
+     * 
+     * Given an {@linkplain Element}, check if there is a Signature direct child element
+     * 
+     * @param element parent {@linkplain Element}
+     * @return true if signature is present
+     */
 
+    public static boolean isSignedElement(Element element) {
+        return getSignature(element) != null;
+    }
+    
+    protected static Element getSignature(Element element) {
+        return DocumentUtil.getDirectChildElement(element, XMLSignature.XMLNS, "Signature");
+    }
+    
     /**
      * Check whether the assertion has expired
      *
@@ -540,7 +572,23 @@ public class AssertionUtil {
         return responseType.getAssertions().get(0).getAssertion();
     }
 
-    public static ResponseType decryptAssertion(ResponseType responseType, PrivateKey privateKey) throws ParsingException, ProcessingException, ConfigurationException {
+    public static boolean isAssertionEncrypted(ResponseType responseType) throws ProcessingException {
+        List<ResponseType.RTChoiceType> assertions = responseType.getAssertions();
+
+        if (assertions.isEmpty()) {
+            throw new ProcessingException("No assertion from response.");
+        }
+
+        ResponseType.RTChoiceType rtChoiceType = assertions.get(0);
+        return rtChoiceType.getEncryptedAssertion() != null;
+    }
+
+    /**
+     * This method modifies the given responseType, and replaces the encrypted assertion with a decrypted version.
+     * @param responseType a response containg an encrypted assertion
+     * @return the assertion element as it was decrypted. This can be used in signature verification.
+     */
+    public static Element decryptAssertion(ResponseType responseType, PrivateKey privateKey) throws ParsingException, ProcessingException, ConfigurationException {
         SAML2Response saml2Response = new SAML2Response();
 
         Document doc = saml2Response.convert(responseType);
@@ -564,6 +612,6 @@ public class AssertionUtil {
 
         responseType.replaceAssertion(oldID, new ResponseType.RTChoiceType(assertion));
 
-        return responseType;
+        return decryptedDocumentElement;
     }
 }
\ No newline at end of file
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java
index 53228c9..7093a20 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java
@@ -468,7 +468,7 @@ public class XMLSignatureUtil {
         return true;
     }
 
-    private static boolean validateSingleNode(Node signatureNode, final KeyLocator locator) throws MarshalException, XMLSignatureException {
+    public static boolean validateSingleNode(Node signatureNode, final KeyLocator locator) throws MarshalException, XMLSignatureException {
         KeySelectorUtilizingKeyNameHint sel = new KeySelectorUtilizingKeyNameHint(locator);
         try {
             if (validateUsingKeySelector(signatureNode, sel)) {
diff --git a/saml-core/src/test/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtilTest.java b/saml-core/src/test/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtilTest.java
new file mode 100644
index 0000000..b60bb4b
--- /dev/null
+++ b/saml-core/src/test/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtilTest.java
@@ -0,0 +1,58 @@
+package org.keycloak.saml.processing.core.saml.v2.util;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.util.Arrays;
+import org.junit.Test;
+import org.keycloak.common.util.Base64;
+import org.keycloak.common.util.DerUtils;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class AssertionUtilTest {
+
+    private static final String PRIVATE_KEY = "MIICWwIBAAKBgQDVG8a7xGN6ZIkDbeecySygcDfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb40tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SVEr55KJoyQJQIDAQABAoGADaTtoG/+foOZUiLjRWKL/OmyavK9vjgyFtThNkZY4qHOh0h3og0RdSbgIxAsIpEa1FUwU2W5yvI6mNeJ3ibFgCgcxqPk6GkAC7DWfQfdQ8cS+dCuaFTs8ObIQEvU50YzeNPiiFxRA+MnauCUXaKm/PnDfjd4tPgru7XZvlGh0wECQQDsBbN2cKkBKpr/b5oJiBcBaSZtWiMNuYBDn9x8uORj+Gy/49BUIMHF2EWyxOWz6ocP5YiynNRkPe21Zus7PEr1AkEA5yWQOkxUTIg43s4pxNSeHtL+Ebqcg54lY2xOQK0yufxUVZI8ODctAKmVBMiCKpU3mZQquOaQicuGtocpgxlScQI/YM31zZ5nsxLGf/5GL6KhzPJT0IYn2nk7IoFu7bjn9BjwgcPurpLA52TNMYWQsTqAKwT6DEhG1NaRqNWNpb4VAkBehObAYBwMm5udyHIeEc+CzUalm0iLLa0eRdiN7AUVNpCJ2V2Uo0NcxPux1AgeP5xXydXafDXYkwhINWcNO9qRAkEA58ckAC5loUGwU5dLaugsGH/a2Q8Ac8bmPglwfCstYDpl8Gp/eimb1eKyvDEELOhyImAv4/uZV9wN85V0xZXWsw==";
+
+    /**
+     * The public certificate that corresponds to {@link #PRIVATE_KEY}.
+     */
+    private static final String PUBLIC_CERT = "MIIDdzCCAl+gAwIBAgIEbySuqTANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYDVQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3duMB4XDTE1MDEyODIyMTYyMFoXDTE3MTAyNDIyMTYyMFowbDEQMA4GA1UEBhMHVW5rbm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UEChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAII/K9NNvXi9IySl7+l2zY/kKrGTtuR4WdCI0xLW/Jn4dLY7v1/HOnV4CC4ecFOzhdNFPtJkmEhP/q62CpmOYOKApXk3tfmm2rwEz9bWprVxgFGKnbrWlz61Z/cjLAlhD3IUj2ZRBquYgSXQPsYfXo1JmSWF5pZ9uh1FVqu9f4wvRqY20ZhUN+39F+1iaBsoqsrbXypCn1HgZkW1/9D9GZug1c3vB4wg1TwZZWRNGtxwoEhdK6dPrNcZ+6PdanVilWrbQFbBjY4wz8/7IMBzssoQ7Usmo8F1Piv0FGfaVeJqBrcAvbiBMpk8pT+27u6p8VyIX6LhGvnxIwM07NByeSUCAwEAAaMhMB8wHQYDVR0OBBYEFFlcNuTYwI9W0tQ224K1gFJlMam0MA0GCSqGSIb3DQEBCwUAA4IBAQB5snl1KWOJALtAjLqD0mLPg1iElmZP82Lq1htLBt3XagwzU9CaeVeCQ7lTp+DXWzPa9nCLhsC3QyrV3/+oqNli8C6NpeqI8FqN2yQW/QMWN1m5jWDbmrWwtQzRUn/rh5KEb5m3zPB+tOC6e/2bV3QeQebxeW7lVMD0tSCviUg1MQf1l2gzuXQo60411YwqrXwk6GMkDOhFDQKDlMchO3oRbQkGbcP8UeiKAXjMeHfzbiBr+cWz8NYZEtxUEDYDjTpKrYCSMJBXpmgVJCZ00BswbksxJwaGqGMPpUKmCV671pf3m8nq3xyiHMDGuGwtbU+GE8kVx85menmp8+964nin";
+
+    @Test
+    public void testSaml20Signed() throws Exception {
+        
+        X509Certificate decodeCertificate = DerUtils.decodeCertificate(new ByteArrayInputStream(Base64.decode(PUBLIC_CERT)));
+        
+        try (InputStream st = AssertionUtilTest.class.getResourceAsStream("saml20-signed-response.xml")) {
+            Document document = DocumentUtil.getDocument(st);
+            
+            Element assertion = DocumentUtil.getDirectChildElement(document.getDocumentElement(), "urn:oasis:names:tc:SAML:2.0:assertion", "Assertion");
+            
+            assertTrue(AssertionUtil.isSignatureValid(assertion, decodeCertificate.getPublicKey()));
+            
+            // test manipulation of signature
+            Element signatureElement = AssertionUtil.getSignature(assertion);
+            byte[] validSignature = Base64.decode(signatureElement.getTextContent());
+            
+            // change the signature value slightly
+            byte[] invalidSignature = Arrays.clone(validSignature);
+            invalidSignature[0] ^= invalidSignature[0];
+            signatureElement.setTextContent(Base64.encodeBytes(invalidSignature));
+            
+            // check that signature now is invalid
+            assertFalse(AssertionUtil.isSignatureValid(document.getDocumentElement(), decodeCertificate.getPublicKey()));
+            
+            // restore valid signature, but remove Signature element, check that still invalid
+            signatureElement.setTextContent(Base64.encodeBytes(validSignature));
+
+            assertion.removeChild(signatureElement);
+            assertFalse(AssertionUtil.isSignatureValid(document.getDocumentElement(), decodeCertificate.getPublicKey()));
+        }
+    }
+
+}
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/saml/v2/util/saml20-signed-response.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/saml/v2/util/saml20-signed-response.xml
new file mode 100644
index 0000000..998520a
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/saml/v2/util/saml20-signed-response.xml
@@ -0,0 +1 @@
+<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/auth/realms/saml-broker-authentication-realm/broker/saml-identity-provider/endpoint" ID="ID_9aca1381-6265-434c-98a5-d89236d32ea0" InResponseTo="ID_2d81575a-0bde-488a-b69d-0b78c0fcf521" IssueInstant="2017-05-04T22:18:13.550Z" Version="2.0"><saml:Issuer>http://localhost:8080/auth/realms/saml-broker-realm</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status><saml:Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion" ID="ID_e9d22a60-7954-48b2-ad97-700d70e332f5" IssueInstant="2017-05-04T22:18:13.546Z" Version="2.0"><saml:Issuer>http://localhost:8080/auth/realms/saml-broker-realm</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_e9d22a60-7954-48b2-ad97-700d70e332f5"><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>cCSNXxLmu411weW1kRpie4C9yaBg2In6V4oEuqya0Eo=</dsig:DigestValue></dsig:Reference></dsig:SignedInfo><dsig:SignatureValue>Qe6ZqgSwFH31UTu+zHqr1/UsafH0luxP5OH/cqyHm07Kf/Fp/fm9mnHJ0kGoUn0SUo7xWvwy8AzUfPXWMYS3kDyhUsPzgz0CnCzzfTz3koKFczgyIQ8sokIDv0cTp3z1qCUVWV0CEPzhtWlaIus2W89TEi/h9KjYrkeGl3+cpm8BPEAt4EP8Oht5czK2haIfPMDUm5Y7uw/FCSsvSfFyrlJ0jR/YMeP9PP0InYYegI9QQgvXKRm6DZSNZgKYFpprc12v6vv/zTaMm5fbuuy1wNDuDTB8EF6K1yrq21DatJXUKE1oOMBrkOvbFJNtgHlQviz1OssAqzHlf0NQPIAEig==</dsig:SignatureValue><dsig:KeyInfo><dsig:KeyName>IzH2UxfMxovYTEHn4Bh-EAj-Zrvldukl_5Snu0RA0B8</dsig:KeyName><dsig:X509Data><dsig:X509Certificate>MIICsTCCAZkCBgFb1GERYzANBgkqhkiG9w0BAQsFADAcMRowGAYDVQQDDBFzYW1sLWJyb2tlci1yZWFsbTAeFw0xNzA1MDQxNjUxMjJaFw0yNzA1MDQxNjUzMDJaMBwxGjAYBgNVBAMMEXNhbWwtYnJva2VyLXJlYWxtMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgj8r0029eL0jJKXv6XbNj+QqsZO25HhZ0IjTEtb8mfh0tju/X8c6dXgILh5wU7OF00U+0mSYSE/+rrYKmY5g4oCleTe1+abavATP1tamtXGAUYqdutaXPrVn9yMsCWEPchSPZlEGq5iBJdA+xh9ejUmZJYXmln26HUVWq71/jC9GpjbRmFQ37f0X7WJoGyiqyttfKkKfUeBmRbX/0P0Zm6DVze8HjCDVPBllZE0a3HCgSF0rp0+s1xn7o91qdWKVattAVsGNjjDPz/sgwHOyyhDtSyajwXU+K/QUZ9pV4moGtwC9uIEymTylP7bu7qnxXIhfouEa+fEjAzTs0HJ5JQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCBuWKmcVPX4sZtpvBp8BgQwH51l5tKPuQq66/4JH40hzkrLOGcVEOafIsGoOWi8HsZWZu+APwhSrnRNd7yMV+NJyw5W1DKjhdUPWnzPNy+UMAcoUBFhXDIWog0qxgGvTdoe1lfHryUQt1cd95SFVIerJA93nFbSOoMB+N7TmfQm+sNu2pJ2tr6mx3wGCXMnWf29gwhCI3wV19hh4KugnMIEStjvQoRyh2yna64BrR3eaUyhU/Bdrq2VXLNU/9WXg9gbRLUEWkMUPKOeQ5cGCgc4JFyFXRo5ExkzmvP9vwBRtjQulk5QKqfYo251mKvTQgO7K8d4CzVS/4+bpgKvZAM</dsig:X509Certificate></dsig:X509Data><dsig:KeyValue><dsig:RSAKeyValue><dsig:Modulus>gj8r0029eL0jJKXv6XbNj+QqsZO25HhZ0IjTEtb8mfh0tju/X8c6dXgILh5wU7OF00U+0mSYSE/+rrYKmY5g4oCleTe1+abavATP1tamtXGAUYqdutaXPrVn9yMsCWEPchSPZlEGq5iBJdA+xh9ejUmZJYXmln26HUVWq71/jC9GpjbRmFQ37f0X7WJoGyiqyttfKkKfUeBmRbX/0P0Zm6DVze8HjCDVPBllZE0a3HCgSF0rp0+s1xn7o91qdWKVattAVsGNjjDPz/sgwHOyyhDtSyajwXU+K/QUZ9pV4moGtwC9uIEymTylP7bu7qnxXIhfouEa+fEjAzTs0HJ5JQ==</dsig:Modulus><dsig:Exponent>AQAB</dsig:Exponent></dsig:RSAKeyValue></dsig:KeyValue></dsig:KeyInfo></dsig:Signature><saml:Subject><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">G-ebb3a66f-686f-4bb9-8a8b-20b566ca747b</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData InResponseTo="ID_2d81575a-0bde-488a-b69d-0b78c0fcf521" NotOnOrAfter="2017-05-04T22:23:11.546Z" Recipient="http://localhost:8080/auth/realms/saml-broker-authentication-realm/broker/saml-identity-provider/endpoint"/></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="2017-05-04T22:18:11.546Z" NotOnOrAfter="2017-05-04T22:19:11.546Z"><saml:AudienceRestriction><saml:Audience>http://localhost:8080/auth/realms/saml-broker-authentication-realm</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant="2017-05-04T22:18:13.551Z" SessionIndex="c0cc880f-83d7-461b-b299-9fb3354f598c"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement><saml:Attribute Name="Role" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">manager</saml:AttributeValue></saml:Attribute><saml:Attribute Name="Role" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">user</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion></samlp:Response>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedDocumentOnlyBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedDocumentOnlyBrokerTest.java
new file mode 100644
index 0000000..5f9fd0e
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedDocumentOnlyBrokerTest.java
@@ -0,0 +1,83 @@
+package org.keycloak.testsuite.broker;
+
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.arquillian.SuiteContext;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.keycloak.testsuite.broker.BrokerTestConstants.*;
+
+public class KcSamlSignedDocumentOnlyBrokerTest extends KcSamlBrokerTest {
+
+    public static class KcSamlSignedBrokerConfiguration extends KcSamlBrokerConfiguration {
+
+        @Override
+        public RealmRepresentation createProviderRealm() {
+            RealmRepresentation realm = super.createProviderRealm();
+
+            realm.setPublicKey(REALM_PUBLIC_KEY);
+            realm.setPrivateKey(REALM_PRIVATE_KEY);
+
+            return realm;
+        }
+
+        @Override
+        public RealmRepresentation createConsumerRealm() {
+            RealmRepresentation realm = super.createConsumerRealm();
+
+            realm.setPublicKey(REALM_PUBLIC_KEY);
+            realm.setPrivateKey(REALM_PRIVATE_KEY);
+
+            return realm;
+        }
+
+        @Override
+        public List<ClientRepresentation> createProviderClients(SuiteContext suiteContext) {
+            List<ClientRepresentation> clientRepresentationList = super.createProviderClients(suiteContext);
+
+            for (ClientRepresentation client : clientRepresentationList) {
+                client.setClientAuthenticatorType("client-secret");
+                client.setSurrogateAuthRequired(false);
+
+                Map<String, String> attributes = client.getAttributes();
+                if (attributes == null) {
+                    attributes = new HashMap<>();
+                    client.setAttributes(attributes);
+                }
+
+                attributes.put("saml.assertion.signature", "false");
+                attributes.put("saml.server.signature", "true");
+                attributes.put("saml.client.signature", "true");
+                attributes.put("saml.signature.algorithm", "RSA_SHA256");
+                attributes.put("saml.signing.private.key", IDP_SAML_SIGN_KEY);
+                attributes.put("saml.signing.certificate", IDP_SAML_SIGN_CERT);
+            }
+
+            return clientRepresentationList;
+        }
+
+        @Override
+        public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
+            IdentityProviderRepresentation result = super.setUpIdentityProvider(suiteContext);
+
+            Map<String, String> config = result.getConfig();
+
+            config.put("validateSignature", "true");
+            config.put("wantAssertionsSigned", "false");
+            config.put("wantAuthnRequestsSigned", "true");
+            config.put("signingCertificate", IDP_SAML_SIGN_CERT);
+
+            return result;
+        }
+    }
+
+    @Override
+    protected BrokerConfiguration getBrokerConfiguration() {
+        return KcSamlSignedBrokerConfiguration.INSTANCE;
+    }
+
+}