Merge pull request #4351 from hmlnarik/KEYCLOAK-4446-Failed-to-process-response-when-reject-consent-with-turned-on-encryption
KEYCLOAK-4775 Added encryption certificate to SAML metadata
This commit is contained in:
commit
121ec2603b
7 changed files with 55 additions and 10 deletions
|
@ -29,6 +29,15 @@ When starting the server it can also import a realm from a json file:
|
||||||
|
|
||||||
mvn exec:java -Pkeycloak-server -Dimport=testrealm.json
|
mvn exec:java -Pkeycloak-server -Dimport=testrealm.json
|
||||||
|
|
||||||
|
When starting the server, https transport can be set up by setting keystore containing the server certificate
|
||||||
|
and https port, optionally setting the truststore.
|
||||||
|
|
||||||
|
mvn exec:java -Pkeycloak-server \
|
||||||
|
-Djavax.net.ssl.trustStore=/path/to/truststore.jks \
|
||||||
|
-Djavax.net.ssl.keyStore=/path/to/keystore.jks \
|
||||||
|
-Djavax.net.ssl.keyStorePassword=CHANGEME \
|
||||||
|
-Dkeycloak.port.https=8443
|
||||||
|
|
||||||
### Live edit of html and styles
|
### Live edit of html and styles
|
||||||
|
|
||||||
The Keycloak test server can load resources directly from the filesystem instead of the classpath. This allows editing html, styles and updating images without restarting the server. To make the server use resources from the filesystem start with:
|
The Keycloak test server can load resources directly from the filesystem instead of the classpath. This allows editing html, styles and updating images without restarting the server. To make the server use resources from the filesystem start with:
|
||||||
|
|
|
@ -23,7 +23,9 @@ package org.keycloak.saml;
|
||||||
*/
|
*/
|
||||||
public class SPMetadataDescriptor {
|
public class SPMetadataDescriptor {
|
||||||
|
|
||||||
public static String getSPDescriptor(String binding, String assertionEndpoint, String logoutEndpoint, boolean wantAuthnRequestsSigned, boolean wantAssertionsSigned, String entityId, String nameIDPolicyFormat, String signingCerts) {
|
public static String getSPDescriptor(String binding, String assertionEndpoint, String logoutEndpoint,
|
||||||
|
boolean wantAuthnRequestsSigned, boolean wantAssertionsSigned, boolean wantAssertionsEncrypted,
|
||||||
|
String entityId, String nameIDPolicyFormat, String signingCerts, String encryptionCerts) {
|
||||||
String descriptor =
|
String descriptor =
|
||||||
"<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"" + entityId + "\">\n" +
|
"<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"" + entityId + "\">\n" +
|
||||||
" <SPSSODescriptor AuthnRequestsSigned=\"" + wantAuthnRequestsSigned + "\" WantAssertionsSigned=\"" + wantAssertionsSigned + "\"\n" +
|
" <SPSSODescriptor AuthnRequestsSigned=\"" + wantAuthnRequestsSigned + "\" WantAssertionsSigned=\"" + wantAssertionsSigned + "\"\n" +
|
||||||
|
@ -31,6 +33,9 @@ public class SPMetadataDescriptor {
|
||||||
if (wantAuthnRequestsSigned && signingCerts != null) {
|
if (wantAuthnRequestsSigned && signingCerts != null) {
|
||||||
descriptor += signingCerts;
|
descriptor += signingCerts;
|
||||||
}
|
}
|
||||||
|
if (wantAssertionsEncrypted && encryptionCerts != null) {
|
||||||
|
descriptor += encryptionCerts;
|
||||||
|
}
|
||||||
descriptor +=
|
descriptor +=
|
||||||
" <SingleLogoutService Binding=\"" + binding + "\" Location=\"" + logoutEndpoint + "\"/>\n" +
|
" <SingleLogoutService Binding=\"" + binding + "\" Location=\"" + logoutEndpoint + "\"/>\n" +
|
||||||
" <NameIDFormat>" + nameIDPolicyFormat + "\n" +
|
" <NameIDFormat>" + nameIDPolicyFormat + "\n" +
|
||||||
|
|
|
@ -54,6 +54,7 @@ import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
|
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
|
||||||
import org.keycloak.keys.KeyMetadata;
|
import org.keycloak.keys.KeyMetadata;
|
||||||
|
import org.keycloak.keys.KeyMetadata.Status;
|
||||||
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
|
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
|
||||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
|
|
||||||
|
@ -237,18 +238,27 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||||
|
|
||||||
boolean wantAuthnRequestsSigned = getConfig().isWantAuthnRequestsSigned();
|
boolean wantAuthnRequestsSigned = getConfig().isWantAuthnRequestsSigned();
|
||||||
boolean wantAssertionsSigned = getConfig().isWantAssertionsSigned();
|
boolean wantAssertionsSigned = getConfig().isWantAssertionsSigned();
|
||||||
|
boolean wantAssertionsEncrypted = getConfig().isWantAssertionsEncrypted();
|
||||||
String entityId = getEntityId(uriInfo, realm);
|
String entityId = getEntityId(uriInfo, realm);
|
||||||
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
|
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
|
||||||
|
|
||||||
StringBuilder keysString = new StringBuilder();
|
StringBuilder signingKeysString = new StringBuilder();
|
||||||
|
StringBuilder encryptionKeysString = new StringBuilder();
|
||||||
Set<RsaKeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
|
Set<RsaKeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
|
||||||
? (int) (o2.getProviderPriority() - o1.getProviderPriority())
|
? (int) (o2.getProviderPriority() - o1.getProviderPriority())
|
||||||
: (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1));
|
: (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1));
|
||||||
keys.addAll(session.keys().getRsaKeys(realm, false));
|
keys.addAll(session.keys().getRsaKeys(realm, false));
|
||||||
for (RsaKeyMetadata key : keys) {
|
for (RsaKeyMetadata key : keys) {
|
||||||
addKeyInfo(keysString, key, KeyTypes.SIGNING.value());
|
addKeyInfo(signingKeysString, key, KeyTypes.SIGNING.value());
|
||||||
|
|
||||||
|
if (key.getStatus() == Status.ACTIVE) {
|
||||||
|
addKeyInfo(encryptionKeysString, key, KeyTypes.ENCRYPTION.value());
|
||||||
}
|
}
|
||||||
String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint, wantAuthnRequestsSigned, wantAssertionsSigned, entityId, nameIDPolicyFormat, keysString.toString());
|
}
|
||||||
|
String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint,
|
||||||
|
wantAuthnRequestsSigned, wantAssertionsSigned, wantAssertionsEncrypted,
|
||||||
|
entityId, nameIDPolicyFormat, signingKeysString.toString(), encryptionKeysString.toString());
|
||||||
|
|
||||||
return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
|
return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -127,7 +127,7 @@ public class SamlIDPDescriptorClientInstallation implements ClientInstallationPr
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getHelpText() {
|
public String getHelpText() {
|
||||||
return "SAML Metadata IDSSODescriptor tailored for the client. This is special because not every client may require things like digital signatures";
|
return "SAML Metadata IDPSSODescriptor tailored for the client. This is special because not every client may require things like digital signatures";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -47,8 +47,10 @@ public class SamlSPDescriptorClientInstallation implements ClientInstallationPro
|
||||||
String nameIdFormat = samlClient.getNameIDFormat();
|
String nameIdFormat = samlClient.getNameIDFormat();
|
||||||
if (nameIdFormat == null) nameIdFormat = SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT;
|
if (nameIdFormat == null) nameIdFormat = SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT;
|
||||||
String spCertificate = SPMetadataDescriptor.xmlKeyInfo(" ", null, samlClient.getClientSigningCertificate(), KeyTypes.SIGNING.value(), true);
|
String spCertificate = SPMetadataDescriptor.xmlKeyInfo(" ", null, samlClient.getClientSigningCertificate(), KeyTypes.SIGNING.value(), true);
|
||||||
|
String encCertificate = SPMetadataDescriptor.xmlKeyInfo(" ", null, samlClient.getClientEncryptingCertificate(), KeyTypes.ENCRYPTION.value(), true);
|
||||||
return SPMetadataDescriptor.getSPDescriptor(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get(), assertionUrl, logoutUrl,
|
return SPMetadataDescriptor.getSPDescriptor(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get(), assertionUrl, logoutUrl,
|
||||||
samlClient.requiresClientSignature(), samlClient.requiresAssertionSignature(), client.getClientId(), nameIdFormat, spCertificate);
|
samlClient.requiresClientSignature(), samlClient.requiresAssertionSignature(), samlClient.requiresEncryption(),
|
||||||
|
client.getClientId(), nameIdFormat, spCertificate, encCertificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -39,7 +39,6 @@ import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.services.resources.KeycloakApplication;
|
import org.keycloak.services.resources.KeycloakApplication;
|
||||||
import org.keycloak.testsuite.util.cli.TestsuiteCLI;
|
import org.keycloak.testsuite.util.cli.TestsuiteCLI;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
import org.mvel2.util.Make;
|
|
||||||
|
|
||||||
import javax.servlet.DispatcherType;
|
import javax.servlet.DispatcherType;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -51,6 +50,7 @@ import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -64,6 +64,7 @@ public class KeycloakServer {
|
||||||
public static class KeycloakServerConfig {
|
public static class KeycloakServerConfig {
|
||||||
private String host = "localhost";
|
private String host = "localhost";
|
||||||
private int port = 8081;
|
private int port = 8081;
|
||||||
|
private int portHttps = -1;
|
||||||
private int workerThreads = Math.max(Runtime.getRuntime().availableProcessors(), 2) * 8;
|
private int workerThreads = Math.max(Runtime.getRuntime().availableProcessors(), 2) * 8;
|
||||||
private String resourcesHome;
|
private String resourcesHome;
|
||||||
|
|
||||||
|
@ -75,6 +76,10 @@ public class KeycloakServer {
|
||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getPortHttps() {
|
||||||
|
return portHttps;
|
||||||
|
}
|
||||||
|
|
||||||
public String getResourcesHome() {
|
public String getResourcesHome() {
|
||||||
return resourcesHome;
|
return resourcesHome;
|
||||||
}
|
}
|
||||||
|
@ -87,6 +92,10 @@ public class KeycloakServer {
|
||||||
this.port = port;
|
this.port = port;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPortHttps(int portHttps) {
|
||||||
|
this.portHttps = portHttps;
|
||||||
|
}
|
||||||
|
|
||||||
public void setResourcesHome(String resourcesHome) {
|
public void setResourcesHome(String resourcesHome) {
|
||||||
this.resourcesHome = resourcesHome;
|
this.resourcesHome = resourcesHome;
|
||||||
}
|
}
|
||||||
|
@ -140,6 +149,10 @@ public class KeycloakServer {
|
||||||
config.setPort(Integer.valueOf(System.getProperty("keycloak.port")));
|
config.setPort(Integer.valueOf(System.getProperty("keycloak.port")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (System.getProperty("keycloak.port.https") != null) {
|
||||||
|
config.setPortHttps(Integer.valueOf(System.getProperty("keycloak.port.https")));
|
||||||
|
}
|
||||||
|
|
||||||
if (System.getProperty("keycloak.bind.address") != null) {
|
if (System.getProperty("keycloak.bind.address") != null) {
|
||||||
config.setHost(System.getProperty("keycloak.bind.address"));
|
config.setHost(System.getProperty("keycloak.bind.address"));
|
||||||
}
|
}
|
||||||
|
@ -312,6 +325,10 @@ public class KeycloakServer {
|
||||||
.setWorkerThreads(config.getWorkerThreads())
|
.setWorkerThreads(config.getWorkerThreads())
|
||||||
.setIoThreads(config.getWorkerThreads() / 8);
|
.setIoThreads(config.getWorkerThreads() / 8);
|
||||||
|
|
||||||
|
if (config.getPortHttps() != -1) {
|
||||||
|
builder = builder.addHttpsListener(config.getPortHttps(), config.getHost(), SSLContext.getDefault());
|
||||||
|
}
|
||||||
|
|
||||||
server = new UndertowJaxrsServer();
|
server = new UndertowJaxrsServer();
|
||||||
try {
|
try {
|
||||||
server.start(builder);
|
server.start(builder);
|
||||||
|
@ -350,7 +367,9 @@ public class KeycloakServer {
|
||||||
info("Loading resources from " + config.getResourcesHome());
|
info("Loading resources from " + config.getResourcesHome());
|
||||||
}
|
}
|
||||||
|
|
||||||
info("Started Keycloak (http://" + config.getHost() + ":" + config.getPort() + "/auth) in "
|
info("Started Keycloak (http://" + config.getHost() + ":" + config.getPort() + "/auth"
|
||||||
|
+ (config.getPortHttps() > 0 ? ", https://" + config.getHost() + ":" + config.getPortHttps()+ "/auth" : "")
|
||||||
|
+ ") in "
|
||||||
+ (System.currentTimeMillis() - start) + " ms\n");
|
+ (System.currentTimeMillis() - start) + " ms\n");
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
server.stop();
|
server.stop();
|
||||||
|
|
|
@ -78,7 +78,7 @@ public class ValidationTest {
|
||||||
public void testBrokerExportDescriptor() throws Exception {
|
public void testBrokerExportDescriptor() throws Exception {
|
||||||
URL schemaFile = getClass().getResource("/schema/saml/v2/saml-schema-metadata-2.0.xsd");
|
URL schemaFile = getClass().getResource("/schema/saml/v2/saml-schema-metadata-2.0.xsd");
|
||||||
Source xmlFile = new StreamSource(new ByteArrayInputStream(SPMetadataDescriptor.getSPDescriptor(
|
Source xmlFile = new StreamSource(new ByteArrayInputStream(SPMetadataDescriptor.getSPDescriptor(
|
||||||
"POST", "http://realm/assertion", "http://realm/logout", true, false, "test", SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT, KeycloakModelUtils.generateKeyPairCertificate("test").getCertificate()
|
"POST", "http://realm/assertion", "http://realm/logout", true, false, false, "test", SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT, KeycloakModelUtils.generateKeyPairCertificate("test").getCertificate(), ""
|
||||||
).getBytes()), "SP Descriptor");
|
).getBytes()), "SP Descriptor");
|
||||||
SchemaFactory schemaFactory = SchemaFactory
|
SchemaFactory schemaFactory = SchemaFactory
|
||||||
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
|
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
|
||||||
|
|
Loading…
Reference in a new issue