keycloak-aplcache
Changes
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java 52(+44 -8)
saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java 72(+60 -12)
saml-core/src/test/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtilTest.java 58(+58 -0)
saml-core/src/test/resources/org/keycloak/saml/processing/core/saml/v2/util/saml20-signed-response.xml 1(+1 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncServlet.java 1(+1 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncSignAssertionsOnlyServlet.java 40(+40 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/ClientAttributeUpdater.java 55(+55 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/IdentityProviderAttributeUpdater.java 55(+55 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/RealmAttributeUpdater.java 55(+55 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/SetSystemProperty.java 60(+60 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java 2(+2 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java 33(+29 -4)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java 25(+16 -9)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerConfiguration.java 38(+20 -18)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java 81(+70 -11)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedDocumentOnlyBrokerTest.java 83(+83 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-enc-sign-assertions-only/WEB-INF/keycloak-saml.xml 65(+65 -0)
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 682f179..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
@@ -51,10 +51,12 @@ import org.keycloak.saml.SAML2AuthnRequestBuilder;
import org.keycloak.saml.SAMLRequestParser;
import org.keycloak.saml.SignatureAlgorithm;
import org.keycloak.saml.common.constants.GeneralConstants;
+import org.keycloak.saml.common.constants.JBossSAMLConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.exceptions.ConfigurationException;
import org.keycloak.saml.common.exceptions.ProcessingException;
import org.keycloak.saml.common.util.Base64;
+import org.keycloak.saml.common.util.DocumentUtil;
import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature;
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
@@ -72,10 +74,14 @@ import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.util.*;
+
+import javax.xml.namespace.QName;
+
import org.keycloak.dom.saml.v2.SAML2Object;
import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
import org.keycloak.rotation.KeyLocator;
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
+import org.keycloak.saml.processing.core.util.XMLEncryptionUtil;
/**
*
@@ -208,7 +214,7 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
return AuthOutcome.FAILED;
}
}
- return handleLoginResponse((ResponseType) statusResponse, postBinding, onCreateSession);
+ return handleLoginResponse(holder, postBinding, onCreateSession);
} finally {
sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.NONE);
}
@@ -310,7 +316,8 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
return false;
}
- protected AuthOutcome handleLoginResponse(final ResponseType responseType, boolean postBinding, OnSessionCreated onCreateSession) {
+ protected AuthOutcome handleLoginResponse(SAMLDocumentHolder responseHolder, boolean postBinding, OnSessionCreated onCreateSession) {
+ final ResponseType responseType = (ResponseType) responseHolder.getSamlObject();
AssertionType assertion = null;
if (! isSuccessfulSamlResponse(responseType) || responseType.getAssertions() == null || responseType.getAssertions().isEmpty()) {
challenge = new AuthChallenge() {
@@ -355,14 +362,33 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
if (deployment.getIDP().getSingleSignOnService().validateAssertionSignature()) {
try {
- validateSamlSignature(new SAMLDocumentHolder(AssertionUtil.asDocument(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() {
+
+ @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;
+ }
+ } catch (Exception e) {
+ log.error("Error processing validation of SAML assertion: " + e.getMessage());
challenge = new AuthChallenge() {
+
@Override
public boolean challenge(HttpFacade exchange) {
- SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.INVALID_SIGNATURE, responseType);
+ SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.EXTRACTION_FAILURE);
exchange.getRequest().setError(error);
exchange.getResponse().sendError(403);
return true;
@@ -374,8 +400,6 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
}
};
return AuthOutcome.FAILED;
- } catch (ProcessingException e) {
- e.printStackTrace();
}
}
@@ -473,6 +497,18 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
&& Objects.equals(responseType.getStatus().getStatusCode().getValue().toString(), JBossSAMLURIConstants.STATUS_SUCCESS.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));
+ return XMLEncryptionUtil.decryptElementInDocument(encryptedAssertionDocument, deployment.getDecryptionKey());
+ }
+ return DocumentUtil.getElement(responseHolder.getSamlDocument(), new QName(JBossSAMLConstants.ASSERTION.get()));
+ }
+
private String getAttributeValue(Object attrValue) {
String value = null;
if (attrValue instanceof String) {
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/parsers/saml/SAMLParserTest.java b/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java
index 52dd7ba..713a5bd 100644
--- 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
@@ -103,9 +103,9 @@ public class SAMLParserTest {
assertNotNull(rtChoiceType.getEncryptedAssertion());
PrivateKey privateKey = DerUtils.decodePrivateKey(Base64.decode(PRIVATE_KEY));
- ResponseType rtWithDecryptedAssertion = AssertionUtil.decryptAssertion(resp, privateKey);
+ AssertionUtil.decryptAssertion(resp, privateKey);
- rtChoiceType = rtWithDecryptedAssertion.getAssertions().get(0);
+ rtChoiceType = resp.getAssertions().get(0);
assertNotNull(rtChoiceType.getAssertion());
assertNull(rtChoiceType.getEncryptedAssertion());
}
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/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
index 38e57cb..af93f28 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
@@ -52,6 +52,7 @@ import org.keycloak.saml.common.constants.GeneralConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.exceptions.ConfigurationException;
import org.keycloak.saml.common.exceptions.ProcessingException;
+import org.keycloak.saml.common.util.DocumentUtil;
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
@@ -83,8 +84,12 @@ import java.util.List;
import org.keycloak.rotation.HardcodedKeyLocator;
import org.keycloak.rotation.KeyLocator;
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
import java.util.*;
+import javax.xml.crypto.dsig.XMLSignature;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -478,6 +483,17 @@ public class SAMLEndpoint {
protected class PostBinding extends Binding {
@Override
protected void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException {
+ NodeList nl = documentHolder.getSamlDocument().getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
+ boolean anyElementSigned = (nl != null && nl.getLength() > 0);
+ if ((! anyElementSigned) && (documentHolder.getSamlObject() instanceof ResponseType)) {
+ ResponseType responseType = (ResponseType) documentHolder.getSamlObject();
+ List<ResponseType.RTChoiceType> assertions = responseType.getAssertions();
+ if (! assertions.isEmpty() ) {
+ // Only relax verification if the response is an authnresponse and contains (encrypted/plaintext) assertion.
+ // In that case, signature is validated on assertion element
+ return;
+ }
+ }
SamlProtocolUtils.verifyDocumentSignature(documentHolder.getSamlDocument(), getIDPKeyLocator());
}
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
index f07495f..46d8b95 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
@@ -27,6 +27,24 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
public static final XmlKeyInfoKeyNameTransformer DEFAULT_XML_KEY_INFO_KEY_NAME_TRANSFORMER = XmlKeyInfoKeyNameTransformer.NONE;
+ public static final String ADD_EXTENSIONS_ELEMENT_WITH_KEY_INFO = "addExtensionsElementWithKeyInfo";
+ public static final String BACKCHANNEL_SUPPORTED = "backchannelSupported";
+ public static final String ENCRYPTION_PUBLIC_KEY = "encryptionPublicKey";
+ public static final String FORCE_AUTHN = "forceAuthn";
+ public static final String NAME_ID_POLICY_FORMAT = "nameIDPolicyFormat";
+ public static final String POST_BINDING_AUTHN_REQUEST = "postBindingAuthnRequest";
+ public static final String POST_BINDING_LOGOUT = "postBindingLogout";
+ public static final String POST_BINDING_RESPONSE = "postBindingResponse";
+ public static final String SIGNATURE_ALGORITHM = "signatureAlgorithm";
+ public static final String SIGNING_CERTIFICATE_KEY = "signingCertificate";
+ public static final String SINGLE_LOGOUT_SERVICE_URL = "singleLogoutServiceUrl";
+ public static final String SINGLE_SIGN_ON_SERVICE_URL = "singleSignOnServiceUrl";
+ public static final String VALIDATE_SIGNATURE = "validateSignature";
+ public static final String WANT_ASSERTIONS_ENCRYPTED = "wantAssertionsEncrypted";
+ public static final String WANT_ASSERTIONS_SIGNED = "wantAssertionsSigned";
+ public static final String WANT_AUTHN_REQUESTS_SIGNED = "wantAuthnRequestsSigned";
+ public static final String XML_SIG_KEY_INFO_KEY_NAME_TRANSFORMER = "xmlSigKeyInfoKeyNameTransformer";
+
public SAMLIdentityProviderConfig() {
}
@@ -35,35 +53,35 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
}
public String getSingleSignOnServiceUrl() {
- return getConfig().get("singleSignOnServiceUrl");
+ return getConfig().get(SINGLE_SIGN_ON_SERVICE_URL);
}
public void setSingleSignOnServiceUrl(String singleSignOnServiceUrl) {
- getConfig().put("singleSignOnServiceUrl", singleSignOnServiceUrl);
+ getConfig().put(SINGLE_SIGN_ON_SERVICE_URL, singleSignOnServiceUrl);
}
public String getSingleLogoutServiceUrl() {
- return getConfig().get("singleLogoutServiceUrl");
+ return getConfig().get(SINGLE_LOGOUT_SERVICE_URL);
}
public void setSingleLogoutServiceUrl(String singleLogoutServiceUrl) {
- getConfig().put("singleLogoutServiceUrl", singleLogoutServiceUrl);
+ getConfig().put(SINGLE_LOGOUT_SERVICE_URL, singleLogoutServiceUrl);
}
public boolean isValidateSignature() {
- return Boolean.valueOf(getConfig().get("validateSignature"));
+ return Boolean.valueOf(getConfig().get(VALIDATE_SIGNATURE));
}
public void setValidateSignature(boolean validateSignature) {
- getConfig().put("validateSignature", String.valueOf(validateSignature));
+ getConfig().put(VALIDATE_SIGNATURE, String.valueOf(validateSignature));
}
public boolean isForceAuthn() {
- return Boolean.valueOf(getConfig().get("forceAuthn"));
+ return Boolean.valueOf(getConfig().get(FORCE_AUTHN));
}
public void setForceAuthn(boolean forceAuthn) {
- getConfig().put("forceAuthn", String.valueOf(forceAuthn));
+ getConfig().put(FORCE_AUTHN, String.valueOf(forceAuthn));
}
/**
@@ -103,70 +121,68 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
return crt.split(",");
}
- public static final String SIGNING_CERTIFICATE_KEY = "signingCertificate";
-
public String getNameIDPolicyFormat() {
- return getConfig().get("nameIDPolicyFormat");
+ return getConfig().get(NAME_ID_POLICY_FORMAT);
}
public void setNameIDPolicyFormat(String nameIDPolicyFormat) {
- getConfig().put("nameIDPolicyFormat", nameIDPolicyFormat);
+ getConfig().put(NAME_ID_POLICY_FORMAT, nameIDPolicyFormat);
}
public boolean isWantAuthnRequestsSigned() {
- return Boolean.valueOf(getConfig().get("wantAuthnRequestsSigned"));
+ return Boolean.valueOf(getConfig().get(WANT_AUTHN_REQUESTS_SIGNED));
}
public void setWantAuthnRequestsSigned(boolean wantAuthnRequestsSigned) {
- getConfig().put("wantAuthnRequestsSigned", String.valueOf(wantAuthnRequestsSigned));
+ getConfig().put(WANT_AUTHN_REQUESTS_SIGNED, String.valueOf(wantAuthnRequestsSigned));
}
public boolean isAddExtensionsElementWithKeyInfo() {
- return Boolean.valueOf(getConfig().get("addExtensionsElementWithKeyInfo"));
+ return Boolean.valueOf(getConfig().get(ADD_EXTENSIONS_ELEMENT_WITH_KEY_INFO));
}
public void setAddExtensionsElementWithKeyInfo(boolean addExtensionsElementWithKeyInfo) {
- getConfig().put("addExtensionsElementWithKeyInfo", String.valueOf(addExtensionsElementWithKeyInfo));
+ getConfig().put(ADD_EXTENSIONS_ELEMENT_WITH_KEY_INFO, String.valueOf(addExtensionsElementWithKeyInfo));
}
public String getSignatureAlgorithm() {
- return getConfig().get("signatureAlgorithm");
+ return getConfig().get(SIGNATURE_ALGORITHM);
}
public void setSignatureAlgorithm(String signatureAlgorithm) {
- getConfig().put("signatureAlgorithm", signatureAlgorithm);
+ getConfig().put(SIGNATURE_ALGORITHM, signatureAlgorithm);
}
public String getEncryptionPublicKey() {
- return getConfig().get("encryptionPublicKey");
+ return getConfig().get(ENCRYPTION_PUBLIC_KEY);
}
public void setEncryptionPublicKey(String encryptionPublicKey) {
- getConfig().put("encryptionPublicKey", encryptionPublicKey);
+ getConfig().put(ENCRYPTION_PUBLIC_KEY, encryptionPublicKey);
}
public boolean isPostBindingAuthnRequest() {
- return Boolean.valueOf(getConfig().get("postBindingAuthnRequest"));
+ return Boolean.valueOf(getConfig().get(POST_BINDING_AUTHN_REQUEST));
}
public void setPostBindingAuthnRequest(boolean postBindingAuthnRequest) {
- getConfig().put("postBindingAuthnRequest", String.valueOf(postBindingAuthnRequest));
+ getConfig().put(POST_BINDING_AUTHN_REQUEST, String.valueOf(postBindingAuthnRequest));
}
public boolean isPostBindingResponse() {
- return Boolean.valueOf(getConfig().get("postBindingResponse"));
+ return Boolean.valueOf(getConfig().get(POST_BINDING_RESPONSE));
}
public void setPostBindingResponse(boolean postBindingResponse) {
- getConfig().put("postBindingResponse", String.valueOf(postBindingResponse));
+ getConfig().put(POST_BINDING_RESPONSE, String.valueOf(postBindingResponse));
}
public boolean isBackchannelSupported() {
- return Boolean.valueOf(getConfig().get("backchannelSupported"));
+ return Boolean.valueOf(getConfig().get(BACKCHANNEL_SUPPORTED));
}
public void setBackchannelSupported(boolean backchannel) {
- getConfig().put("backchannelSupported", String.valueOf(backchannel));
+ getConfig().put(BACKCHANNEL_SUPPORTED, String.valueOf(backchannel));
}
/**
@@ -174,11 +190,11 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
* @return Configured ransformer of {@link #DEFAULT_XML_KEY_INFO_KEY_NAME_TRANSFORMER} if not set.
*/
public XmlKeyInfoKeyNameTransformer getXmlSigKeyInfoKeyNameTransformer() {
- return XmlKeyInfoKeyNameTransformer.from(getConfig().get("xmlSigKeyInfoKeyNameTransformer"), DEFAULT_XML_KEY_INFO_KEY_NAME_TRANSFORMER);
+ return XmlKeyInfoKeyNameTransformer.from(getConfig().get(XML_SIG_KEY_INFO_KEY_NAME_TRANSFORMER), DEFAULT_XML_KEY_INFO_KEY_NAME_TRANSFORMER);
}
public void setXmlSigKeyInfoKeyNameTransformer(XmlKeyInfoKeyNameTransformer xmlSigKeyInfoKeyNameTransformer) {
- getConfig().put("xmlSigKeyInfoKeyNameTransformer",
+ getConfig().put(XML_SIG_KEY_INFO_KEY_NAME_TRANSFORMER,
xmlSigKeyInfoKeyNameTransformer == null
? null
: xmlSigKeyInfoKeyNameTransformer.name());
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
index cabbcb4..2e1fea3 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -193,7 +193,7 @@ public class SamlProtocol implements LoginProtocol {
if (samlClient.requiresEncryption()) {
PublicKey publicKey;
try {
- publicKey = SamlProtocolUtils.getEncryptionValidationKey(client);
+ publicKey = SamlProtocolUtils.getEncryptionKey(client);
} catch (Exception e) {
logger.error("failed", e);
return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE);
@@ -446,7 +446,7 @@ public class SamlProtocol implements LoginProtocol {
if (samlClient.requiresEncryption()) {
PublicKey publicKey = null;
try {
- publicKey = SamlProtocolUtils.getEncryptionValidationKey(client);
+ publicKey = SamlProtocolUtils.getEncryptionKey(client);
} catch (Exception e) {
logger.error("failed", e);
return ErrorPage.error(session, Messages.FAILED_TO_PROCESS_RESPONSE);
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
index 026a54a..7ab97a4 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
@@ -103,7 +103,7 @@ public class SamlProtocolUtils {
* @return Public key for encryption.
* @throws VerificationException
*/
- public static PublicKey getEncryptionValidationKey(ClientModel client) throws VerificationException {
+ public static PublicKey getEncryptionKey(ClientModel client) throws VerificationException {
return getPublicKey(client, SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE);
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncServlet.java
index 874b1e8..82a29fe 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncServlet.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncServlet.java
@@ -27,6 +27,7 @@ import java.net.URL;
*/
public class SalesPostEncServlet extends SAMLServlet {
public static final String DEPLOYMENT_NAME = "sales-post-enc";
+ public static final String CLIENT_NAME = "http://localhost:8081/sales-post-enc/";
@ArquillianResource
@OperateOnDeployment(DEPLOYMENT_NAME)
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncSignAssertionsOnlyServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncSignAssertionsOnlyServlet.java
new file mode 100644
index 0000000..cb44ac2
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostEncSignAssertionsOnlyServlet.java
@@ -0,0 +1,40 @@
+/*
+ * 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.testsuite.adapter.page;
+
+import org.jboss.arquillian.container.test.api.OperateOnDeployment;
+import org.jboss.arquillian.test.api.ArquillianResource;
+
+import java.net.URL;
+
+/**
+ * @author mhajas
+ */
+public class SalesPostEncSignAssertionsOnlyServlet extends SAMLServlet {
+ public static final String DEPLOYMENT_NAME = "sales-post-enc-sign-assertions-only";
+ public static final String CLIENT_NAME = "http://localhost:8081/sales-post-enc-sign-assertions-only/";
+
+ @ArquillianResource
+ @OperateOnDeployment(DEPLOYMENT_NAME)
+ private URL url;
+
+ @Override
+ public URL getInjectedUrl() {
+ return url;
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/ClientAttributeUpdater.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/ClientAttributeUpdater.java
new file mode 100644
index 0000000..0272a1b
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/ClientAttributeUpdater.java
@@ -0,0 +1,55 @@
+package org.keycloak.testsuite.updaters;
+
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.representations.idm.ClientRepresentation;
+import java.io.Closeable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class ClientAttributeUpdater {
+
+ private final Map<String, String> originalAttributes = new HashMap<>();
+
+ private final ClientResource clientResource;
+
+ private final ClientRepresentation rep;
+
+ public ClientAttributeUpdater(ClientResource clientResource) {
+ this.clientResource = clientResource;
+ this.rep = clientResource.toRepresentation();
+ if (this.rep.getAttributes() == null) {
+ this.rep.setAttributes(new HashMap<>());
+ }
+ }
+
+ public ClientAttributeUpdater setAttribute(String name, String value) {
+ if (! originalAttributes.containsKey(name)) {
+ this.originalAttributes.put(name, this.rep.getAttributes().put(name, value));
+ } else {
+ this.rep.getAttributes().put(name, value);
+ }
+ return this;
+ }
+
+ public ClientAttributeUpdater removeAttribute(String name) {
+ if (! originalAttributes.containsKey(name)) {
+ this.originalAttributes.put(name, this.rep.getAttributes().put(name, null));
+ } else {
+ this.rep.getAttributes().put(name, null);
+ }
+ return this;
+ }
+
+ public Closeable update() {
+ clientResource.update(rep);
+
+ return () -> {
+ rep.getAttributes().putAll(originalAttributes);
+ clientResource.update(rep);
+ };
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/IdentityProviderAttributeUpdater.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/IdentityProviderAttributeUpdater.java
new file mode 100644
index 0000000..b8eb487
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/IdentityProviderAttributeUpdater.java
@@ -0,0 +1,55 @@
+package org.keycloak.testsuite.updaters;
+
+import org.keycloak.admin.client.resource.IdentityProviderResource;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import java.io.Closeable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class IdentityProviderAttributeUpdater {
+
+ private final Map<String, String> originalAttributes = new HashMap<>();
+
+ private final IdentityProviderResource identityProviderResource;
+
+ private final IdentityProviderRepresentation rep;
+
+ public IdentityProviderAttributeUpdater(IdentityProviderResource identityProviderResource) {
+ this.identityProviderResource = identityProviderResource;
+ this.rep = identityProviderResource.toRepresentation();
+ if (this.rep.getConfig() == null) {
+ this.rep.setConfig(new HashMap<>());
+ }
+ }
+
+ public IdentityProviderAttributeUpdater setAttribute(String name, String value) {
+ if (! originalAttributes.containsKey(name)) {
+ this.originalAttributes.put(name, this.rep.getConfig().put(name, value));
+ } else {
+ this.rep.getConfig().put(name, value);
+ }
+ return this;
+ }
+
+ public IdentityProviderAttributeUpdater removeAttribute(String name) {
+ if (! originalAttributes.containsKey(name)) {
+ this.originalAttributes.put(name, this.rep.getConfig().put(name, null));
+ } else {
+ this.rep.getConfig().put(name, null);
+ }
+ return this;
+ }
+
+ public Closeable update() {
+ identityProviderResource.update(rep);
+
+ return () -> {
+ rep.getConfig().putAll(originalAttributes);
+ identityProviderResource.update(rep);
+ };
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/RealmAttributeUpdater.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/RealmAttributeUpdater.java
new file mode 100644
index 0000000..72c2c4b
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/RealmAttributeUpdater.java
@@ -0,0 +1,55 @@
+package org.keycloak.testsuite.updaters;
+
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.representations.idm.RealmRepresentation;
+import java.io.Closeable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class RealmAttributeUpdater {
+
+ private final Map<String, String> originalAttributes = new HashMap<>();
+
+ private final RealmResource realmResource;
+
+ private final RealmRepresentation rep;
+
+ public RealmAttributeUpdater(RealmResource realmResource) {
+ this.realmResource = realmResource;
+ this.rep = realmResource.toRepresentation();
+ if (this.rep.getAttributes() == null) {
+ this.rep.setAttributes(new HashMap<>());
+ }
+ }
+
+ public RealmAttributeUpdater setAttribute(String name, String value) {
+ if (! originalAttributes.containsKey(name)) {
+ this.originalAttributes.put(name, this.rep.getAttributes().put(name, value));
+ } else {
+ this.rep.getAttributes().put(name, value);
+ }
+ return this;
+ }
+
+ public RealmAttributeUpdater removeAttribute(String name) {
+ if (! originalAttributes.containsKey(name)) {
+ this.originalAttributes.put(name, this.rep.getAttributes().put(name, null));
+ } else {
+ this.rep.getAttributes().put(name, null);
+ }
+ return this;
+ }
+
+ public Closeable update() {
+ realmResource.update(rep);
+
+ return () -> {
+ rep.getAttributes().putAll(originalAttributes);
+ realmResource.update(rep);
+ };
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/SetSystemProperty.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/SetSystemProperty.java
new file mode 100644
index 0000000..9a798b2
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/SetSystemProperty.java
@@ -0,0 +1,60 @@
+/*
+ * 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.testsuite.updaters;
+
+import java.io.Closeable;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SetSystemProperty implements Closeable {
+
+ private final String name;
+ private final String oldValue;
+
+ public SetSystemProperty(String name, String value) {
+ this.name = name;
+ this.oldValue = System.getProperty(name);
+
+ if (value == null) {
+ if (oldValue != null) {
+ System.getProperties().remove(name);
+ }
+ } else {
+ System.setProperty(name, value);
+ }
+ }
+
+ public void revert() {
+ String value = System.getProperty(name);
+
+ if (oldValue == null) {
+ if (value != null) {
+ System.getProperties().remove(name);
+ }
+ } else {
+ System.setProperty(name, oldValue);
+ }
+ }
+
+ @Override
+ public void close() {
+ revert();
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java
index ce92fb8..70ef820 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java
@@ -24,6 +24,7 @@ public abstract class AbstractSAMLFilterServletAdapterTest extends AbstractSAMLS
salesMetadataServletPage.checkRoles(true);
salesPostServletPage.checkRoles(true);
salesPostEncServletPage.checkRoles(true);
+ salesPostEncSignAssertionsOnlyServletPage.checkRoles(true);
salesPostSigServletPage.checkRoles(true);
salesPostPassiveServletPage.checkRoles(true);
salesPostSigPersistentServletPage.checkRoles(true);
@@ -56,6 +57,7 @@ public abstract class AbstractSAMLFilterServletAdapterTest extends AbstractSAMLS
salesMetadataServletPage.checkRoles(false);
salesPostServletPage.checkRoles(false);
salesPostEncServletPage.checkRoles(false);
+ salesPostEncSignAssertionsOnlyServletPage.checkRoles(false);
salesPostSigServletPage.checkRoles(false);
salesPostPassiveServletPage.checkRoles(false);
salesPostSigEmailServletPage.checkRoles(false);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
index 32d3a9b..2927ec8 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
@@ -17,6 +17,7 @@
package org.keycloak.testsuite.adapter.servlet;
+import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
@@ -88,14 +89,13 @@ import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.ByteArrayInputStream;
+import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.security.KeyPair;
import java.security.PublicKey;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.*;
@@ -109,7 +109,6 @@ import static org.keycloak.testsuite.util.IOUtil.loadXML;
import static org.keycloak.testsuite.util.IOUtil.modifyDocElementAttribute;
import static org.keycloak.testsuite.util.Matchers.bodyHC;
import static org.keycloak.testsuite.util.Matchers.statusCodeIsHC;
-import static org.keycloak.testsuite.util.SamlClient.Binding.POST;
import static org.keycloak.testsuite.util.SamlClient.idpInitiatedLogin;
import static org.keycloak.testsuite.util.SamlClient.login;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
@@ -159,6 +158,9 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
protected SalesPostEncServlet salesPostEncServletPage;
@Page
+ protected SalesPostEncSignAssertionsOnlyServlet salesPostEncSignAssertionsOnlyServletPage;
+
+ @Page
protected SalesPostPassiveServlet salesPostPassiveServletPage;
@Page
@@ -261,6 +263,11 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
return samlServletDeployment(SalesPostEncServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
}
+ @Deployment(name = SalesPostEncSignAssertionsOnlyServlet.DEPLOYMENT_NAME)
+ protected static WebArchive salesPostEncSignAssertionsOnly() {
+ return samlServletDeployment(SalesPostEncSignAssertionsOnlyServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
+ }
+
@Deployment(name = SalesPostPassiveServlet.DEPLOYMENT_NAME)
protected static WebArchive salesPostPassive() {
return samlServletDeployment(SalesPostPassiveServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
@@ -628,6 +635,24 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
}
@Test
+ public void salesPostEncSignedAssertionsOnlyTest() throws Exception {
+ testSuccessfulAndUnauthorizedLogin(salesPostEncSignAssertionsOnlyServletPage, testRealmSAMLPostLoginPage);
+ }
+
+ @Test
+ public void salesPostEncSignedAssertionsAndDocumentTest() throws Exception {
+ ClientRepresentation salesPostEncClient = testRealmResource().clients().findByClientId(SalesPostEncServlet.CLIENT_NAME).get(0);
+ try (Closeable client = new ClientAttributeUpdater(testRealmResource().clients().get(salesPostEncClient.getId()))
+ .setAttribute(SamlConfigAttributes.SAML_ASSERTION_SIGNATURE, "true")
+ .setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE, "true")
+ .update()) {
+ testSuccessfulAndUnauthorizedLogin(salesPostEncServletPage, testRealmSAMLPostLoginPage);
+ } finally {
+ salesPostEncServletPage.logout();
+ }
+ }
+
+ @Test
public void salesPostPassiveTest() {
salesPostPassiveServletPage.navigateTo();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java
index f6e575f..71596af 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractBrokerTest.java
@@ -38,6 +38,8 @@ import static org.keycloak.testsuite.broker.BrokerTestTools.*;
public abstract class AbstractBrokerTest extends AbstractBaseBrokerTest {
+ protected IdentityProviderResource identityProviderResource;
+
@Before
public void createUser() {
log.debug("creating user for realm " + bc.providerRealmName());
@@ -59,7 +61,8 @@ public abstract class AbstractBrokerTest extends AbstractBaseBrokerTest {
log.debug("adding identity provider to realm " + bc.consumerRealmName());
RealmResource realm = adminClient.realm(bc.consumerRealmName());
- realm.identityProviders().create(bc.setUpIdentityProvider(suiteContext));
+ realm.identityProviders().create(bc.setUpIdentityProvider(suiteContext)).close();
+ identityProviderResource = realm.identityProviders().get(bc.getIDPAlias());
}
@Before
@@ -70,7 +73,7 @@ public abstract class AbstractBrokerTest extends AbstractBaseBrokerTest {
for (ClientRepresentation client : clients) {
log.debug("adding client " + client.getName() + " to realm " + bc.providerRealmName());
- providerRealm.clients().create(client);
+ providerRealm.clients().create(client).close();
}
}
@@ -80,7 +83,7 @@ public abstract class AbstractBrokerTest extends AbstractBaseBrokerTest {
for (ClientRepresentation client : clients) {
log.debug("adding client " + client.getName() + " to realm " + bc.consumerRealmName());
- consumerRealm.clients().create(client);
+ consumerRealm.clients().create(client).close();
}
}
}
@@ -88,6 +91,12 @@ public abstract class AbstractBrokerTest extends AbstractBaseBrokerTest {
@Test
public void testLogInAsUserInIDP() {
+ loginUser();
+
+ testSingleLogout();
+ }
+
+ protected void loginUser() {
driver.navigate().to(getAccountUrl(bc.consumerRealmName()));
log.debug("Clicking social " + bc.getIDPAlias());
@@ -96,16 +105,16 @@ public abstract class AbstractBrokerTest extends AbstractBaseBrokerTest {
waitForPage(driver, "log in to");
Assert.assertTrue("Driver should be on the provider realm page right now",
- driver.getCurrentUrl().contains("/auth/realms/" + bc.providerRealmName() + "/"));
+ driver.getCurrentUrl().contains("/auth/realms/" + bc.providerRealmName() + "/"));
log.debug("Logging in");
accountLoginPage.login(bc.getUserLogin(), bc.getUserPassword());
waitForPage(driver, "update account information");
- Assert.assertTrue(updateAccountInformationPage.isCurrent());
+ updateAccountInformationPage.assertCurrent();
Assert.assertTrue("We must be on correct realm right now",
- driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/"));
+ driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/"));
log.debug("Updating info on updateAccount page");
updateAccountInformationPage.updateAccountInformation(bc.getUserLogin(), bc.getUserEmail(), "Firstname", "Lastname");
@@ -126,9 +135,7 @@ public abstract class AbstractBrokerTest extends AbstractBaseBrokerTest {
}
Assert.assertTrue("There must be user " + bc.getUserLogin() + " in realm " + bc.consumerRealmName(),
- isUserFound);
-
- testSingleLogout();
+ isUserFound);
}
@Test
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerConfiguration.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerConfiguration.java
index e5f5b8d..da0bc2b 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerConfiguration.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlBrokerConfiguration.java
@@ -6,6 +6,7 @@
package org.keycloak.testsuite.broker;
import org.keycloak.protocol.ProtocolMapperUtils;
+import org.keycloak.protocol.saml.SamlConfigAttributes;
import org.keycloak.protocol.saml.SamlProtocol;
import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
import org.keycloak.protocol.saml.mappers.UserAttributeStatementMapper;
@@ -22,6 +23,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import static org.keycloak.broker.saml.SAMLIdentityProviderConfig.*;
import static org.keycloak.testsuite.broker.BrokerTestConstants.*;
import static org.keycloak.testsuite.broker.BrokerTestTools.*;
@@ -63,17 +65,17 @@ public class KcSamlBrokerConfiguration implements BrokerConfiguration {
Map<String, String> attributes = new HashMap<>();
- attributes.put("saml.authnstatement", "true");
- attributes.put("saml_single_logout_service_url_post",
+ attributes.put(SamlConfigAttributes.SAML_AUTHNSTATEMENT, "true");
+ attributes.put(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE,
getAuthRoot(suiteContext) + "/auth/realms/" + REALM_CONS_NAME + "/broker/" + IDP_SAML_ALIAS + "/endpoint");
- attributes.put("saml_assertion_consumer_url_post",
+ attributes.put(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE,
getAuthRoot(suiteContext) + "/auth/realms/" + REALM_CONS_NAME + "/broker/" + IDP_SAML_ALIAS + "/endpoint");
- attributes.put("saml_force_name_id_format", "true");
- attributes.put("saml_name_id_format", "username");
- attributes.put("saml.assertion.signature", "false");
- attributes.put("saml.server.signature", "false");
- attributes.put("saml.client.signature", "false");
- attributes.put("saml.encrypt", "false");
+ attributes.put(SamlConfigAttributes.SAML_FORCE_NAME_ID_FORMAT_ATTRIBUTE, "true");
+ attributes.put(SamlConfigAttributes.SAML_NAME_ID_FORMAT_ATTRIBUTE, "username");
+ attributes.put(SamlConfigAttributes.SAML_ASSERTION_SIGNATURE, "false");
+ attributes.put(SamlConfigAttributes.SAML_SERVER_SIGNATURE, "false");
+ attributes.put(SamlConfigAttributes.SAML_CLIENT_SIGNATURE_ATTRIBUTE, "false");
+ attributes.put(SamlConfigAttributes.SAML_ENCRYPT, "false");
client.setAttributes(attributes);
@@ -133,15 +135,15 @@ public class KcSamlBrokerConfiguration implements BrokerConfiguration {
Map<String, String> config = idp.getConfig();
- config.put("singleSignOnServiceUrl", getAuthRoot(suiteContext) + "/auth/realms/" + REALM_PROV_NAME + "/protocol/saml");
- config.put("singleLogoutServiceUrl", getAuthRoot(suiteContext) + "/auth/realms/" + REALM_PROV_NAME + "/protocol/saml");
- config.put("nameIDPolicyFormat", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress");
- config.put("forceAuthn", "true");
- config.put("postBindingResponse", "true");
- config.put("postBindingAuthnRequest", "true");
- config.put("validateSignature", "false");
- config.put("wantAuthnRequestsSigned", "false");
- config.put("backchannelSupported", "true");
+ config.put(SINGLE_SIGN_ON_SERVICE_URL, getAuthRoot(suiteContext) + "/auth/realms/" + REALM_PROV_NAME + "/protocol/saml");
+ config.put(SINGLE_LOGOUT_SERVICE_URL, getAuthRoot(suiteContext) + "/auth/realms/" + REALM_PROV_NAME + "/protocol/saml");
+ config.put(NAME_ID_POLICY_FORMAT, "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress");
+ config.put(FORCE_AUTHN, "true");
+ config.put(POST_BINDING_RESPONSE, "true");
+ config.put(POST_BINDING_AUTHN_REQUEST, "true");
+ config.put(VALIDATE_SIGNATURE, "false");
+ config.put(WANT_AUTHN_REQUESTS_SIGNED, "false");
+ config.put(BACKCHANNEL_SUPPORTED, "true");
return idp;
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java
index b4825cd..ef23a9a 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java
@@ -1,19 +1,29 @@
package org.keycloak.testsuite.broker;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.broker.saml.SAMLIdentityProviderConfig;
+import org.keycloak.protocol.saml.SamlConfigAttributes;
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 org.keycloak.testsuite.updaters.ClientAttributeUpdater;
+import org.keycloak.testsuite.updaters.IdentityProviderAttributeUpdater;
+import java.io.Closeable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Test;
import static org.keycloak.testsuite.broker.BrokerTestConstants.*;
+import static org.keycloak.testsuite.broker.BrokerTestTools.encodeUrl;
public class KcSamlSignedBrokerTest extends KcSamlBrokerTest {
- public static class KcSamlSignedBrokerConfiguration extends KcSamlBrokerConfiguration {
+ public class KcSamlSignedBrokerConfiguration extends KcSamlBrokerConfiguration {
@Override
public RealmRepresentation createProviderRealm() {
@@ -39,6 +49,9 @@ public class KcSamlSignedBrokerTest extends KcSamlBrokerTest {
public List<ClientRepresentation> createProviderClients(SuiteContext suiteContext) {
List<ClientRepresentation> clientRepresentationList = super.createProviderClients(suiteContext);
+ String consumerCert = adminClient.realm(consumerRealmName()).keys().getKeyMetadata().getKeys().get(0).getCertificate();
+ Assert.assertThat(consumerCert, Matchers.notNullValue());
+
for (ClientRepresentation client : clientRepresentationList) {
client.setClientAuthenticatorType("client-secret");
client.setSurrogateAuthRequired(false);
@@ -49,12 +62,11 @@ public class KcSamlSignedBrokerTest extends KcSamlBrokerTest {
client.setAttributes(attributes);
}
- attributes.put("saml.assertion.signature", "true");
- 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);
+ attributes.put(SamlConfigAttributes.SAML_ASSERTION_SIGNATURE, "true");
+ attributes.put(SamlConfigAttributes.SAML_SERVER_SIGNATURE, "true");
+ attributes.put(SamlConfigAttributes.SAML_CLIENT_SIGNATURE_ATTRIBUTE, "true");
+ attributes.put(SamlConfigAttributes.SAML_SIGNATURE_ALGORITHM, "RSA_SHA256");
+ attributes.put(SamlConfigAttributes.SAML_SIGNING_CERTIFICATE_ATTRIBUTE, consumerCert);
}
return clientRepresentationList;
@@ -64,11 +76,15 @@ public class KcSamlSignedBrokerTest extends KcSamlBrokerTest {
public IdentityProviderRepresentation setUpIdentityProvider(SuiteContext suiteContext) {
IdentityProviderRepresentation result = super.setUpIdentityProvider(suiteContext);
+ String providerCert = adminClient.realm(providerRealmName()).keys().getKeyMetadata().getKeys().get(0).getCertificate();
+ Assert.assertThat(providerCert, Matchers.notNullValue());
+
Map<String, String> config = result.getConfig();
- config.put("validateSignature", "true");
- config.put("wantAuthnRequestsSigned", "true");
- config.put("signingCertificate", IDP_SAML_SIGN_CERT);
+ config.put(SAMLIdentityProviderConfig.VALIDATE_SIGNATURE, "true");
+ config.put(SAMLIdentityProviderConfig.WANT_ASSERTIONS_SIGNED, "true");
+ config.put(SAMLIdentityProviderConfig.WANT_AUTHN_REQUESTS_SIGNED, "true");
+ config.put(SAMLIdentityProviderConfig.SIGNING_CERTIFICATE_KEY, providerCert);
return result;
}
@@ -76,7 +92,50 @@ public class KcSamlSignedBrokerTest extends KcSamlBrokerTest {
@Override
protected BrokerConfiguration getBrokerConfiguration() {
- return KcSamlSignedBrokerConfiguration.INSTANCE;
+ return new KcSamlSignedBrokerConfiguration();
}
+ @Test
+ public void testSignedEncryptedAssertions() throws Exception {
+ ClientRepresentation client = adminClient.realm(bc.providerRealmName())
+ .clients()
+ .findByClientId(bc.getIDPClientIdInProviderRealm(suiteContext))
+ .get(0);
+
+ final ClientResource clientResource = realmsResouce().realm(bc.providerRealmName()).clients().get(client.getId());
+ Assert.assertThat(clientResource, Matchers.notNullValue());
+
+ String providerCert = adminClient.realm(bc.providerRealmName()).keys().getKeyMetadata().getKeys().get(0).getCertificate();
+ Assert.assertThat(providerCert, Matchers.notNullValue());
+
+ String consumerCert = adminClient.realm(bc.consumerRealmName()).keys().getKeyMetadata().getKeys().get(0).getCertificate();
+ Assert.assertThat(consumerCert, Matchers.notNullValue());
+
+ try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
+ .setAttribute(SAMLIdentityProviderConfig.VALIDATE_SIGNATURE, "true")
+ .setAttribute(SAMLIdentityProviderConfig.WANT_ASSERTIONS_SIGNED, "true")
+ .setAttribute(SAMLIdentityProviderConfig.WANT_ASSERTIONS_ENCRYPTED, "true")
+ .setAttribute(SAMLIdentityProviderConfig.WANT_AUTHN_REQUESTS_SIGNED, "false")
+ .setAttribute(SAMLIdentityProviderConfig.SIGNING_CERTIFICATE_KEY, providerCert)
+ .update();
+ Closeable clientUpdater = new ClientAttributeUpdater(clientResource)
+ .setAttribute(SamlConfigAttributes.SAML_ENCRYPT, "true")
+ .setAttribute(SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE, consumerCert)
+ .setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE, "false") // only sign assertions
+ .setAttribute(SamlConfigAttributes.SAML_ASSERTION_SIGNATURE, "true")
+ .setAttribute(SamlConfigAttributes.SAML_CLIENT_SIGNATURE_ATTRIBUTE, "false")
+ .update())
+ {
+ // Login should pass because assertion is signed.
+ loginUser();
+
+ // Logout should fail because logout response is not signed.
+ driver.navigate().to(BrokerTestTools.getAuthRoot(suiteContext)
+ + "/auth/realms/" + bc.providerRealmName()
+ + "/protocol/" + "openid-connect"
+ + "/logout?redirect_uri=" + encodeUrl(getAccountUrl(bc.providerRealmName())));
+
+ errorPage.assertCurrent();
+ }
+ }
}
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;
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-enc-sign-assertions-only/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-enc-sign-assertions-only/WEB-INF/keycloak-saml.xml
new file mode 100644
index 0000000..39df7d9
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-enc-sign-assertions-only/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,65 @@
+<!--
+ ~ 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.
+ -->
+
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="urn:keycloak:saml:adapter http://www.keycloak.org/schema/keycloak_saml_adapter_1_7.xsd">
+ <SP entityID="http://localhost:8081/sales-post-enc-sign-assertions-only/"
+ sslPolicy="EXTERNAL"
+ nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+ logoutPage="/logout.jsp"
+ forceAuthentication="false">
+ <Keys>
+ <Key signing="true" encryption="true">
+ <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+ <PrivateKey alias="http://localhost:8080/sales-post-enc/" password="test123"/>
+ <Certificate alias="http://localhost:8080/sales-post-enc/"/>
+ </KeyStore>
+ </Key>
+ </Keys>
+ <PrincipalNameMapping policy="FROM_NAME_ID"/>
+ <RoleIdentifiers>
+ <Attribute name="Role"/>
+ </RoleIdentifiers>
+ <IDP entityID="idp">
+ <SingleSignOnService signRequest="true"
+ validateResponseSignature="false"
+ validateAssertionSignature="true"
+ requestBinding="POST"
+ bindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+ />
+
+ <SingleLogoutService
+ validateRequestSignature="true"
+ validateResponseSignature="false"
+ signRequest="true"
+ signResponse="true"
+ requestBinding="POST"
+ responseBinding="POST"
+ postBindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+ redirectBindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+ />
+ <Keys>
+ <Key signing="true" >
+ <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+ <Certificate alias="demo"/>
+ </KeyStore>
+ </Key>
+ </Keys>
+ </IDP>
+ </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-enc-sign-assertions-only/WEB-INF/keystore.jks b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-enc-sign-assertions-only/WEB-INF/keystore.jks
new file mode 100644
index 0000000..822162c
Binary files /dev/null and b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-enc-sign-assertions-only/WEB-INF/keystore.jks differ
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json
index bb0e0d1..2604eb7 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json
@@ -317,6 +317,25 @@
}
},
{
+ "clientId": "http://localhost:8081/sales-post-enc-sign-assertions-only/",
+ "enabled": true,
+ "protocol": "saml",
+ "fullScopeAllowed": true,
+ "baseUrl": "http://localhost:8080/sales-post-enc-sign-assertions-only",
+ "redirectUris": [
+ ],
+ "attributes": {
+ "saml.server.signature": "false",
+ "saml.assertion.signature": "true",
+ "saml.signature.algorithm": "RSA_SHA512",
+ "saml.client.signature": "true",
+ "saml.encrypt": "true",
+ "saml.authnstatement": "true",
+ "saml.signing.certificate": "MIIB1DCCAT0CBgFJGVacCDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1lbmMvMB4XDTE0MTAxNjE0MjA0NloXDTI0MTAxNjE0MjIyNlowMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3QtZW5jLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA2+5MCT5BnVN+IYnKZcH6ev1pjXGi4feE0nOycq/VJ3aeaZMi4G9AxOxCBPupErOC7Kgm/Bw5AdJyw+Q12wSRXfJ9FhqCrLXpb7YOhbVSTJ8De5O8mW35DxAlh/cxe9FXjqPb286wKTUZ3LfGYR+X235UQeCTAPS/Ufi21EXaEikCAwEAATANBgkqhkiG9w0BAQsFAAOBgQBMrfGD9QFfx5v7ld/OAto5rjkTe3R1Qei8XRXfcs83vLaqEzjEtTuLGrJEi55kXuJgBpVmQpnwCCkkjSy0JxbqLDdVi9arfWUxEGmOr01ZHycELhDNaQcFqVMPr5kRHIHgktT8hK2IgCvd3Fy9/JCgUgCPxKfhwecyEOKxUc857g==",
+ "saml.encryption.certificate": "MIIB1DCCAT0CBgFJGVacCDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1lbmMvMB4XDTE0MTAxNjE0MjA0NloXDTI0MTAxNjE0MjIyNlowMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3QtZW5jLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA2+5MCT5BnVN+IYnKZcH6ev1pjXGi4feE0nOycq/VJ3aeaZMi4G9AxOxCBPupErOC7Kgm/Bw5AdJyw+Q12wSRXfJ9FhqCrLXpb7YOhbVSTJ8De5O8mW35DxAlh/cxe9FXjqPb286wKTUZ3LfGYR+X235UQeCTAPS/Ufi21EXaEikCAwEAATANBgkqhkiG9w0BAQsFAAOBgQBMrfGD9QFfx5v7ld/OAto5rjkTe3R1Qei8XRXfcs83vLaqEzjEtTuLGrJEi55kXuJgBpVmQpnwCCkkjSy0JxbqLDdVi9arfWUxEGmOr01ZHycELhDNaQcFqVMPr5kRHIHgktT8hK2IgCvd3Fy9/JCgUgCPxKfhwecyEOKxUc857g=="
+ }
+ },
+ {
"clientId": "http://localhost:8081/employee-sig/",
"enabled": true,
"protocol": "saml",