Merge pull request #3472 from hmlnarik/KEYCLOAK-1881-saml-key-rotation

Keycloak 1881 - SAML key/cert rotation for IdP
This commit is contained in:
Stian Thorgersen authored and GitHub committed 2016-11-08 07:56:25 +01:00
commit 292777259e
121 files changed
+4327 -446

No files matched your search

@@ -38,7 +38,7 @@ import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.keycloak.common.util.EnvUtil;
import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.representations.adapters.config.AdapterHttpClientConfig;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
@@ -333,7 +333,7 @@ public class HttpClientBuilder {
}
}
public HttpClient build(AdapterConfig adapterConfig) {
public HttpClient build(AdapterHttpClientConfig adapterConfig) {
disableCookieCache(); // disable cookie cache as we don't want sticky sessions for load balancing
String truststorePath = adapterConfig.getTruststore();
@@ -379,13 +379,13 @@ public class HttpClientBuilder {
/**
* Configures a the proxy to use for auth-server requests if provided.
* <p>
* If the given {@link AdapterConfig} contains the attribute {@code proxy-url} we use the
* If the given {@link AdapterHttpClientConfig} contains the attribute {@code proxy-url} we use the
* given URL as a proxy server, otherwise the proxy configuration is ignored.
* </p>
*
* @param adapterConfig
*/
private void configureProxyForAuthServerIfProvided(AdapterConfig adapterConfig) {
private void configureProxyForAuthServerIfProvided(AdapterHttpClientConfig adapterConfig) {
if (adapterConfig == null || adapterConfig.getProxyUrl() == null || adapterConfig.getProxyUrl().trim().isEmpty()) {
return;
Whitespace-only changes.
+6
View File
@@ -34,6 +34,7 @@
<timestamp>${maven.build.timestamp}</timestamp>
<maven.build.timestamp.format>yyyy-MM-dd HH:mm</maven.build.timestamp.format>
</properties>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
@@ -70,6 +71,11 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
@@ -0,0 +1,75 @@
/*
* 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.adapters.cloned;
/**
* Configuration options relevant for configuring http client that can be used by adapter.
*
* NOTE: keep in sync with core/src/main/java/org/keycloak/representations/adapters/config/AdapterHttpClientConfig.java until unified.
*
* @author hmlnarik
*/
public interface AdapterHttpClientConfig {
/**
* Returns truststore filename.
*/
public String getTruststore();
/**
* Returns truststore password.
*/
public String getTruststorePassword();
/**
* Returns keystore with client keys.
*/
public String getClientKeystore();
/**
* Returns keystore password.
*/
public String getClientKeystorePassword();
/**
* Returns boolean flag whether any hostname verification is done on the server's
* certificate, {@code true} means that verification is not done.
* @return
*/
public boolean isAllowAnyHostname();
/**
* Returns boolean flag whether any trust management and hostname verification is done.
* <p>
* <i>NOTE</i> Disabling trust manager is a security hole, so only set this option
* if you cannot or do not want to verify the identity of the
* host you are communicating with.
*/
public boolean isDisableTrustManager();
/**
* Returns size of connection pool.
*/
public int getConnectionPoolSize();
/**
* Returns URL of HTTP proxy.
*/
public String getProxyUrl();
}
@@ -0,0 +1,78 @@
/*
* 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.adapters.cloned;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import java.io.IOException;
import java.io.InputStream;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.util.EntityUtils;
import org.keycloak.adapters.saml.descriptor.parsers.SamlDescriptorIDPKeysExtractor;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.saml.common.exceptions.ParsingException;
/**
* @author <a href="mailto:hmlnarik@redhat.com">Hynek Mlnařík</a>
*/
public class HttpAdapterUtils {
public static MultivaluedHashMap<String, KeyInfo> downloadKeysFromSamlDescriptor(HttpClient client, String descriptorUrl) throws HttpClientAdapterException {
try {
HttpGet httpRequest = new HttpGet(descriptorUrl);
HttpResponse response = client.execute(httpRequest);
int status = response.getStatusLine().getStatusCode();
if (status != HttpStatus.SC_OK) {
EntityUtils.consumeQuietly(response.getEntity());
throw new HttpClientAdapterException("Unexpected status = " + status);
}
HttpEntity entity = response.getEntity();
if (entity == null) {
throw new HttpClientAdapterException("There was no entity.");
}
MultivaluedHashMap<String, KeyInfo> res;
try (InputStream is = entity.getContent()) {
res = extractKeysFromSamlDescriptor(is);
}
EntityUtils.consumeQuietly(entity);
return res;
} catch (IOException | ParsingException e) {
throw new HttpClientAdapterException("IO error", e);
}
}
/**
* Parses SAML descriptor and extracts keys from it.
* @param xmlStream
* @return List of KeyInfo objects containing keys from the descriptor.
* @throws IOException
*/
public static MultivaluedHashMap<String, KeyInfo> extractKeysFromSamlDescriptor(InputStream xmlStream) throws ParsingException {
Object res = new SamlDescriptorIDPKeysExtractor().parse(xmlStream);
return (MultivaluedHashMap<String, KeyInfo>) res;
}
}
@@ -0,0 +1,32 @@
/*
* 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.adapters.cloned;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class HttpClientAdapterException extends Exception {
public HttpClientAdapterException(String message) {
super(message);
}
public HttpClientAdapterException(String message, Throwable t) {
super(message, t);
}
}
@@ -0,0 +1,396 @@
/*
* 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.adapters.cloned;
import org.apache.http.HttpHost;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.StrictHostnameVerifier;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.SingleClientConnManager;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.keycloak.common.util.EnvUtil;
import org.keycloak.common.util.KeystoreUtil;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.URI;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Abstraction for creating HttpClients. Allows SSL configuration.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class HttpClientBuilder {
public static enum HostnameVerificationPolicy {
/**
* Hostname verification is not done on the server's certificate
*/
ANY,
/**
* Allows wildcards in subdomain names i.e. *.foo.com
*/
WILDCARD,
/**
* CN must match hostname connecting to
*/
STRICT
}
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
private static class PassthroughTrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
protected KeyStore truststore;
protected KeyStore clientKeyStore;
protected String clientPrivateKeyPassword;
protected boolean disableTrustManager;
protected boolean disableCookieCache = true;
protected HostnameVerificationPolicy policy = HostnameVerificationPolicy.WILDCARD;
protected SSLContext sslContext;
protected int connectionPoolSize = 100;
protected int maxPooledPerRoute = 0;
protected long connectionTTL = -1;
protected TimeUnit connectionTTLUnit = TimeUnit.MILLISECONDS;
protected HostnameVerifier verifier = null;
protected long socketTimeout = -1;
protected TimeUnit socketTimeoutUnits = TimeUnit.MILLISECONDS;
protected long establishConnectionTimeout = -1;
protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS;
protected HttpHost proxyHost;
/**
* Socket inactivity timeout
*
* @param timeout
* @param unit
* @return
*/
public HttpClientBuilder socketTimeout(long timeout, TimeUnit unit) {
this.socketTimeout = timeout;
this.socketTimeoutUnits = unit;
return this;
}
/**
* When trying to make an initial socket connection, what is the timeout?
*
* @param timeout
* @param unit
* @return
*/
public HttpClientBuilder establishConnectionTimeout(long timeout, TimeUnit unit) {
this.establishConnectionTimeout = timeout;
this.establishConnectionTimeoutUnits = unit;
return this;
}
public HttpClientBuilder connectionTTL(long ttl, TimeUnit unit) {
this.connectionTTL = ttl;
this.connectionTTLUnit = unit;
return this;
}
public HttpClientBuilder maxPooledPerRoute(int maxPooledPerRoute) {
this.maxPooledPerRoute = maxPooledPerRoute;
return this;
}
public HttpClientBuilder connectionPoolSize(int connectionPoolSize) {
this.connectionPoolSize = connectionPoolSize;
return this;
}
/**
* Disable trust management and hostname verification. <i>NOTE</i> this is a security
* hole, so only set this option if you cannot or do not want to verify the identity of the
* host you are communicating with.
*/
public HttpClientBuilder disableTrustManager() {
this.disableTrustManager = true;
return this;
}
public HttpClientBuilder disableCookieCache() {
this.disableCookieCache = true;
return this;
}
/**
* SSL policy used to verify hostnames
*
* @param policy
* @return
*/
public HttpClientBuilder hostnameVerification(HostnameVerificationPolicy policy) {
this.policy = policy;
return this;
}
public HttpClientBuilder sslContext(SSLContext sslContext) {
this.sslContext = sslContext;
return this;
}
public HttpClientBuilder trustStore(KeyStore truststore) {
this.truststore = truststore;
return this;
}
public HttpClientBuilder keyStore(KeyStore keyStore, String password) {
this.clientKeyStore = keyStore;
this.clientPrivateKeyPassword = password;
return this;
}
public HttpClientBuilder keyStore(KeyStore keyStore, char[] password) {
this.clientKeyStore = keyStore;
this.clientPrivateKeyPassword = new String(password);
return this;
}
static class VerifierWrapper implements X509HostnameVerifier {
protected HostnameVerifier verifier;
VerifierWrapper(HostnameVerifier verifier) {
this.verifier = verifier;
}
@Override
public void verify(String host, SSLSocket ssl) throws IOException {
if (!verifier.verify(host, ssl.getSession())) throw new SSLException("Hostname verification failure");
}
@Override
public void verify(String host, X509Certificate cert) throws SSLException {
throw new SSLException("This verification path not implemented");
}
@Override
public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
throw new SSLException("This verification path not implemented");
}
@Override
public boolean verify(String s, SSLSession sslSession) {
return verifier.verify(s, sslSession);
}
}
public HttpClient build() {
X509HostnameVerifier verifier = null;
if (this.verifier != null) verifier = new VerifierWrapper(this.verifier);
else {
switch (policy) {
case ANY:
verifier = new AllowAllHostnameVerifier();
break;
case WILDCARD:
verifier = new BrowserCompatHostnameVerifier();
break;
case STRICT:
verifier = new StrictHostnameVerifier();
break;
}
}
try {
SSLSocketFactory sslsf = null;
SSLContext theContext = sslContext;
if (disableTrustManager) {
theContext = SSLContext.getInstance("SSL");
theContext.init(null, new TrustManager[]{new PassthroughTrustManager()},
new SecureRandom());
verifier = new AllowAllHostnameVerifier();
sslsf = new SniSSLSocketFactory(theContext, verifier);
} else if (theContext != null) {
sslsf = new SniSSLSocketFactory(theContext, verifier);
} else if (clientKeyStore != null || truststore != null) {
sslsf = new SniSSLSocketFactory(SSLSocketFactory.TLS, clientKeyStore, clientPrivateKeyPassword, truststore, null, verifier);
} else {
final SSLContext tlsContext = SSLContext.getInstance(SSLSocketFactory.TLS);
tlsContext.init(null, null, null);
sslsf = new SniSSLSocketFactory(tlsContext, verifier);
}
SchemeRegistry registry = new SchemeRegistry();
registry.register(
new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
Scheme httpsScheme = new Scheme("https", 443, sslsf);
registry.register(httpsScheme);
ClientConnectionManager cm = null;
if (connectionPoolSize > 0) {
ThreadSafeClientConnManager tcm = new ThreadSafeClientConnManager(registry, connectionTTL, connectionTTLUnit);
tcm.setMaxTotal(connectionPoolSize);
if (maxPooledPerRoute == 0) maxPooledPerRoute = connectionPoolSize;
tcm.setDefaultMaxPerRoute(maxPooledPerRoute);
cm = tcm;
} else {
cm = new SingleClientConnManager(registry);
}
BasicHttpParams params = new BasicHttpParams();
if (proxyHost != null) {
params.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxyHost);
}
if (socketTimeout > -1) {
HttpConnectionParams.setSoTimeout(params, (int) socketTimeoutUnits.toMillis(socketTimeout));
}
if (establishConnectionTimeout > -1) {
HttpConnectionParams.setConnectionTimeout(params, (int) establishConnectionTimeoutUnits.toMillis(establishConnectionTimeout));
}
DefaultHttpClient client = new DefaultHttpClient(cm, params);
if (disableCookieCache) {
client.setCookieStore(new CookieStore() {
@Override
public void addCookie(Cookie cookie) {
//To change body of implemented methods use File | Settings | File Templates.
}
@Override
public List<Cookie> getCookies() {
return Collections.emptyList();
}
@Override
public boolean clearExpired(Date date) {
return false; //To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void clear() {
//To change body of implemented methods use File | Settings | File Templates.
}
});
}
return client;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public HttpClient build(AdapterHttpClientConfig adapterConfig) {
disableCookieCache(); // disable cookie cache as we don't want sticky sessions for load balancing
String truststorePath = adapterConfig.getTruststore();
if (truststorePath != null) {
truststorePath = EnvUtil.replace(truststorePath);
String truststorePassword = adapterConfig.getTruststorePassword();
try {
this.truststore = KeystoreUtil.loadKeyStore(truststorePath, truststorePassword);
} catch (Exception e) {
throw new RuntimeException("Failed to load truststore", e);
}
}
String clientKeystore = adapterConfig.getClientKeystore();
if (clientKeystore != null) {
clientKeystore = EnvUtil.replace(clientKeystore);
String clientKeystorePassword = adapterConfig.getClientKeystorePassword();
try {
KeyStore clientCertKeystore = KeystoreUtil.loadKeyStore(clientKeystore, clientKeystorePassword);
keyStore(clientCertKeystore, clientKeystorePassword);
} catch (Exception e) {
throw new RuntimeException("Failed to load keystore", e);
}
}
int size = 10;
if (adapterConfig.getConnectionPoolSize() > 0)
size = adapterConfig.getConnectionPoolSize();
HttpClientBuilder.HostnameVerificationPolicy policy = HttpClientBuilder.HostnameVerificationPolicy.WILDCARD;
if (adapterConfig.isAllowAnyHostname())
policy = HttpClientBuilder.HostnameVerificationPolicy.ANY;
connectionPoolSize(size);
hostnameVerification(policy);
if (adapterConfig.isDisableTrustManager()) {
disableTrustManager();
} else {
trustStore(truststore);
}
configureProxyForAuthServerIfProvided(adapterConfig);
return build();
}
/**
* Configures a the proxy to use for auth-server requests if provided.
* <p>
* If the given {@link AdapterHttpClientConfig} contains the attribute {@code proxy-url} we use the
* given URL as a proxy server, otherwise the proxy configuration is ignored.
* </p>
*
* @param adapterConfig
*/
private void configureProxyForAuthServerIfProvided(AdapterHttpClientConfig adapterConfig) {
if (adapterConfig == null || adapterConfig.getProxyUrl() == null || adapterConfig.getProxyUrl().trim().isEmpty()) {
return;
}
URI uri = URI.create(adapterConfig.getProxyUrl());
this.proxyHost = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
}
}
@@ -0,0 +1,143 @@
/*
* 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.adapters.cloned;
import org.apache.http.HttpHost;
import org.apache.http.conn.scheme.HostNameResolver;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.protocol.HttpContext;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.security.AccessController;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* SSLSocketFactory that uses Server Name Indication (SNI) TLS extension.
*
* <p>
* Originally copied from <b>keycloak-adapter-core</b> project.
*
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
* @author <a href="mailto:hmlnarik@redhat.com">Hynek Mlnařík</a>
*/
public class SniSSLSocketFactory extends SSLSocketFactory {
private static final Logger LOG = Logger.getLogger(SniSSLSocketFactory.class.getName());
public SniSSLSocketFactory(String algorithm, KeyStore keystore, String keyPassword, KeyStore truststore, SecureRandom random, HostNameResolver nameResolver) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(algorithm, keystore, keyPassword, truststore, random, nameResolver);
}
public SniSSLSocketFactory(String algorithm, KeyStore keystore, String keyPassword, KeyStore truststore, SecureRandom random, TrustStrategy trustStrategy, X509HostnameVerifier hostnameVerifier) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(algorithm, keystore, keyPassword, truststore, random, trustStrategy, hostnameVerifier);
}
public SniSSLSocketFactory(String algorithm, KeyStore keystore, String keyPassword, KeyStore truststore, SecureRandom random, X509HostnameVerifier hostnameVerifier) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(algorithm, keystore, keyPassword, truststore, random, hostnameVerifier);
}
public SniSSLSocketFactory(KeyStore keystore, String keystorePassword, KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(keystore, keystorePassword, truststore);
}
public SniSSLSocketFactory(KeyStore keystore, String keystorePassword) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(keystore, keystorePassword);
}
public SniSSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(truststore);
}
public SniSSLSocketFactory(TrustStrategy trustStrategy, X509HostnameVerifier hostnameVerifier) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(trustStrategy, hostnameVerifier);
}
public SniSSLSocketFactory(TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(trustStrategy);
}
public SniSSLSocketFactory(SSLContext sslContext) {
super(sslContext);
}
public SniSSLSocketFactory(SSLContext sslContext, HostNameResolver nameResolver) {
super(sslContext, nameResolver);
}
public SniSSLSocketFactory(SSLContext sslContext, X509HostnameVerifier hostnameVerifier) {
super(sslContext, hostnameVerifier);
}
public SniSSLSocketFactory(SSLContext sslContext, String[] supportedProtocols, String[] supportedCipherSuites, X509HostnameVerifier hostnameVerifier) {
super(sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifier);
}
public SniSSLSocketFactory(javax.net.ssl.SSLSocketFactory socketfactory, X509HostnameVerifier hostnameVerifier) {
super(socketfactory, hostnameVerifier);
}
public SniSSLSocketFactory(javax.net.ssl.SSLSocketFactory socketfactory, String[] supportedProtocols, String[] supportedCipherSuites, X509HostnameVerifier hostnameVerifier) {
super(socketfactory, supportedProtocols, supportedCipherSuites, hostnameVerifier);
}
@Override
public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress, InetSocketAddress localAddress, HttpContext context) throws IOException {
return super.connectSocket(connectTimeout, applySNI(socket, host.getHostName()), host, remoteAddress, localAddress, context);
}
@Override
public Socket createLayeredSocket(Socket socket, String target, int port, HttpContext context) throws IOException {
return super.createLayeredSocket(applySNI(socket, target), target, port, context);
}
private Socket applySNI(final Socket socket, String hostname) {
if (socket instanceof SSLSocket) {
try {
Method setHostMethod = AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() {
@Override
public Method run() throws NoSuchMethodException {
return socket.getClass().getMethod("setHost", String.class);
}
});
setHostMethod.invoke(socket, hostname);
LOG.log(Level.FINEST, "Applied SNI to socket for host {0}", hostname);
} catch (PrivilegedActionException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
LOG.log(Level.WARNING, "Failed to apply SNI to SSLSocket", e);
}
}
return socket;
}
}
@@ -79,7 +79,9 @@ public abstract class AbstractInitiateLogin implements AuthChallenge {
binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
}
binding.signWith(keypair);
binding.signWith(null, keypair);
// TODO: As part of KEYCLOAK-3810, add KeyID to the SAML document
// <related DocumentBuilder>.addExtension(new KeycloakKeySamlExtensionGenerator(<key ID>));
binding.signDocument();
}
return binding;
@@ -23,7 +23,14 @@ import org.keycloak.saml.SignatureAlgorithm;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.apache.http.client.HttpClient;
import org.keycloak.adapters.saml.rotation.SamlDescriptorPublicKeyLocator;
import org.keycloak.rotation.CompositeKeyLocator;
import org.keycloak.rotation.HardcodedKeyLocator;
import org.keycloak.rotation.KeyLocator;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -179,10 +186,15 @@ public class DefaultSamlDeployment implements SamlDeployment {
public static class DefaultIDP implements IDP {
private static final int DEFAULT_CACHE_TTL = 24 * 60 * 60;
private String entityID;
private PublicKey signatureValidationKey;
private final CompositeKeyLocator signatureValidationKeyLocator = new CompositeKeyLocator();
private SingleSignOnService singleSignOnService;
private SingleLogoutService singleLogoutService;
private final List<PublicKey> signatureValidationKeys = new LinkedList<>();
private int minTimeBetweenDescriptorRequests;
private HttpClient client;
@Override
public String getEntityID() {
@@ -200,16 +212,25 @@ public class DefaultSamlDeployment implements SamlDeployment {
}
@Override
public PublicKey getSignatureValidationKey() {
return signatureValidationKey;
public KeyLocator getSignatureValidationKeyLocator() {
return this.signatureValidationKeyLocator;
}
@Override
public int getMinTimeBetweenDescriptorRequests() {
return minTimeBetweenDescriptorRequests;
}
public void setMinTimeBetweenDescriptorRequests(int minTimeBetweenDescriptorRequests) {
this.minTimeBetweenDescriptorRequests = minTimeBetweenDescriptorRequests;
}
public void setEntityID(String entityID) {
this.entityID = entityID;
}
public void setSignatureValidationKey(PublicKey signatureValidationKey) {
this.signatureValidationKey = signatureValidationKey;
public void addSignatureValidationKey(PublicKey signatureValidationKey) {
this.signatureValidationKeys.add(signatureValidationKey);
}
public void setSingleSignOnService(SingleSignOnService singleSignOnService) {
@@ -219,6 +240,31 @@ public class DefaultSamlDeployment implements SamlDeployment {
public void setSingleLogoutService(SingleLogoutService singleLogoutService) {
this.singleLogoutService = singleLogoutService;
}
public void refreshKeyLocatorConfiguration() {
this.signatureValidationKeyLocator.clear();
// When key is set, use that (and only that), otherwise configure dynamic key locator
if (! this.signatureValidationKeys.isEmpty()) {
this.signatureValidationKeyLocator.add(new HardcodedKeyLocator(this.signatureValidationKeys));
} else if (this.singleSignOnService != null) {
String samlDescriptorUrl = singleSignOnService.getRequestBindingUrl() + "/descriptor";
HttpClient httpClient = getClient();
SamlDescriptorPublicKeyLocator samlDescriptorPublicKeyLocator =
new SamlDescriptorPublicKeyLocator(
samlDescriptorUrl, this.minTimeBetweenDescriptorRequests, DEFAULT_CACHE_TTL, httpClient);
this.signatureValidationKeyLocator.add(samlDescriptorPublicKeyLocator);
}
}
@Override
public HttpClient getClient() {
return this.client;
}
public void setClient(HttpClient client) {
this.client = client;
}
}
private IDP idp;
@@ -22,14 +22,18 @@ import org.keycloak.saml.SignatureAlgorithm;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Set;
import org.apache.http.client.HttpClient;
import org.keycloak.rotation.KeyLocator;
/**
* Represents SAML deployment configuration.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface SamlDeployment {
enum Binding {
POST,
REDIRECT;
@@ -41,20 +45,68 @@ public interface SamlDeployment {
}
public interface IDP {
/**
* Returns entity identifier of this IdP.
* @return see description.
*/
String getEntityID();
/**
* Returns Single sign on service configuration for this IdP.
* @return see description.
*/
SingleSignOnService getSingleSignOnService();
/**
* Returns Single logout service configuration for this IdP.
* @return see description.
*/
SingleLogoutService getSingleLogoutService();
PublicKey getSignatureValidationKey();
/**
* Returns {@link KeyLocator} looking up public keys used for validation of IdP signatures.
* @return see description.
*/
KeyLocator getSignatureValidationKeyLocator();
/**
* Returns minimum time (in seconds) between issuing requests to IdP SAML descriptor.
* Used e.g. by {@link KeyLocator} looking up public keys for validation of IdP signatures
* to prevent too frequent requests.
*
* @return see description.
*/
int getMinTimeBetweenDescriptorRequests();
/**
* Returns {@link HttpClient} instance that will be used for http communication with this IdP.
* @return see description
*/
HttpClient getClient();
public interface SingleSignOnService {
/**
* Returns {@code true} if the requests to IdP need to be signed by SP key.
* @return see dscription
*/
boolean signRequest();
/**
* Returns {@code true} if the complete response message from IdP should
* be checked for valid signature.
* @return see dscription
*/
boolean validateResponseSignature();
/**
* Returns {@code true} if individual assertions in response from IdP should
* be checked for valid signature.
* @return see dscription
*/
boolean validateAssertionSignature();
Binding getRequestBinding();
Binding getResponseBinding();
String getRequestBindingUrl();
}
public interface SingleLogoutService {
boolean validateRequestSignature();
boolean validateResponseSignature();
@@ -67,10 +119,19 @@ public interface SamlDeployment {
}
}
/**
* Returns Identity Provider configuration for this SAML deployment.
* @return see description.
*/
public IDP getIDP();
public boolean isConfigured();
SslRequired getSslRequired();
/**
* Returns entity identifier of this SP.
* @return see description.
*/
String getEntityID();
String getNameIDPolicyFormat();
boolean isForceAuthentication();
@@ -19,6 +19,7 @@ package org.keycloak.adapters.saml.config;
import java.io.Serializable;
import java.util.List;
import org.keycloak.adapters.cloned.AdapterHttpClientConfig;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -157,12 +158,97 @@ public class IDP implements Serializable {
}
}
public static class HttpClientConfig implements AdapterHttpClientConfig {
private String truststore;
private String truststorePassword;
private String clientKeystore;
private String clientKeystorePassword;
private boolean allowAnyHostname;
private boolean disableTrustManager;
private int connectionPoolSize;
private String proxyUrl;
@Override
public String getTruststore() {
return truststore;
}
public void setTruststore(String truststore) {
this.truststore = truststore;
}
@Override
public String getTruststorePassword() {
return truststorePassword;
}
public void setTruststorePassword(String truststorePassword) {
this.truststorePassword = truststorePassword;
}
@Override
public String getClientKeystore() {
return clientKeystore;
}
public void setClientKeystore(String clientKeystore) {
this.clientKeystore = clientKeystore;
}
@Override
public String getClientKeystorePassword() {
return clientKeystorePassword;
}
public void setClientKeystorePassword(String clientKeystorePassword) {
this.clientKeystorePassword = clientKeystorePassword;
}
@Override
public boolean isAllowAnyHostname() {
return allowAnyHostname;
}
public void setAllowAnyHostname(boolean allowAnyHostname) {
this.allowAnyHostname = allowAnyHostname;
}
@Override
public boolean isDisableTrustManager() {
return disableTrustManager;
}
public void setDisableTrustManager(boolean disableTrustManager) {
this.disableTrustManager = disableTrustManager;
}
@Override
public int getConnectionPoolSize() {
return connectionPoolSize;
}
public void setConnectionPoolSize(int connectionPoolSize) {
this.connectionPoolSize = connectionPoolSize;
}
@Override
public String getProxyUrl() {
return proxyUrl;
}
public void setProxyUrl(String proxyUrl) {
this.proxyUrl = proxyUrl;
}
}
private String entityID;
private String signatureAlgorithm;
private String signatureCanonicalizationMethod;
private SingleSignOnService singleSignOnService;
private SingleLogoutService singleLogoutService;
private List<Key> keys;
private AdapterHttpClientConfig httpClientConfig = new HttpClientConfig();
public String getEntityID() {
return entityID;
@@ -212,4 +298,12 @@ public class IDP implements Serializable {
this.signatureCanonicalizationMethod = signatureCanonicalizationMethod;
}
public AdapterHttpClientConfig getHttpClientConfig() {
return httpClientConfig;
}
public void setHttpClientConfig(AdapterHttpClientConfig httpClientConfig) {
this.httpClientConfig = httpClientConfig;
}
}
@@ -72,4 +72,15 @@ public class ConfigXmlConstants {
public static final String VALIDATE_REQUEST_SIGNATURE_ATTR = "validateRequestSignature";
public static final String POST_BINDING_URL_ATTR = "postBindingUrl";
public static final String REDIRECT_BINDING_URL_ATTR = "redirectBindingUrl";
public static final String HTTP_CLIENT_ELEMENT = "HttpClient";
public static final String ALLOW_ANY_HOSTNAME_ATTR = "allowAnyHostname";
public static final String CLIENT_KEYSTORE_ATTR = "clientKeystore";
public static final String CLIENT_KEYSTORE_PASSWORD_ATTR = "clientKeystorePassword";
public static final String CONNECTION_POOL_SIZE_ATTR = "connectionPoolSize";
public static final String DISABLE_TRUST_MANAGER_ATTR = "disableTrustManager";
public static final String PROXY_URL_ATTR = "proxyUrl";
public static final String TRUSTSTORE_ATTR = "truststore";
public static final String TRUSTSTORE_PASSWORD_ATTR = "truststorePassword";
}
@@ -40,6 +40,7 @@ import java.security.PublicKey;
import java.security.cert.Certificate;
import java.util.HashSet;
import java.util.Set;
import org.keycloak.adapters.cloned.HttpClientBuilder;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -178,35 +179,39 @@ public class DeploymentBuilder {
if (sp.getIdp().getKeys() != null) {
for (Key key : sp.getIdp().getKeys()) {
if (key.isSigning()) {
if (key.getKeystore() != null) {
KeyStore keyStore = loadKeystore(resourceLoader, key);
Certificate cert = null;
try {
cert = keyStore.getCertificate(key.getKeystore().getCertificateAlias());
} catch (KeyStoreException e) {
throw new RuntimeException(e);
}
idp.setSignatureValidationKey(cert.getPublicKey());
} else {
if (key.getPublicKeyPem() == null && key.getCertificatePem() == null) {
throw new RuntimeException("IDP signing key must have a PublicKey or Certificate defined");
}
try {
PublicKey publicKey = getPublicKeyFromPem(key);
idp.setSignatureValidationKey(publicKey);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
processSigningKey(idp, key, resourceLoader);
}
}
}
idp.setClient(new HttpClientBuilder().build(sp.getIdp().getHttpClientConfig()));
idp.refreshKeyLocatorConfiguration();
return deployment;
}
protected static PublicKey getPublicKeyFromPem(Key key) throws Exception {
private void processSigningKey(DefaultSamlDeployment.DefaultIDP idp, Key key, ResourceLoader resourceLoader) throws RuntimeException {
PublicKey publicKey;
if (key.getKeystore() != null) {
KeyStore keyStore = loadKeystore(resourceLoader, key);
Certificate cert = null;
try {
cert = keyStore.getCertificate(key.getKeystore().getCertificateAlias());
} catch (KeyStoreException e) {
throw new RuntimeException(e);
}
publicKey = cert.getPublicKey();
} else {
if (key.getPublicKeyPem() == null && key.getCertificatePem() == null) {
throw new RuntimeException("IDP signing key must have a PublicKey or Certificate defined");
}
publicKey = getPublicKeyFromPem(key);
}
idp.addSignatureValidationKey(publicKey);
}
protected static PublicKey getPublicKeyFromPem(Key key) {
PublicKey publicKey;
if (key.getPublicKeyPem() != null) {
publicKey = PemUtils.decodePublicKey(key.getPublicKeyPem().trim());
@@ -29,6 +29,10 @@ import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import java.util.List;
import org.keycloak.adapters.saml.config.IDP.HttpClientConfig;
import static org.keycloak.adapters.saml.config.parsers.SPXmlParser.getAttributeValue;
import static org.keycloak.adapters.saml.config.parsers.SPXmlParser.getBooleanAttributeValue;
import static org.keycloak.adapters.saml.config.parsers.SPXmlParser.getIntegerAttributeValue;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -41,16 +45,16 @@ public class IDPXmlParser extends AbstractParser {
StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
StaxParserUtil.validate(startElement, ConfigXmlConstants.IDP_ELEMENT);
IDP idp = new IDP();
String entityID = SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.ENTITY_ID_ATTR);
String entityID = getAttributeValue(startElement, ConfigXmlConstants.ENTITY_ID_ATTR);
if (entityID == null) {
throw new ParsingException("entityID must be set on IDP");
}
idp.setEntityID(entityID);
boolean signaturesRequired = SPXmlParser.getBooleanAttributeValue(startElement, ConfigXmlConstants.SIGNATURES_REQUIRED_ATTR);
idp.setSignatureCanonicalizationMethod(SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_CANONICALIZATION_METHOD_ATTR));
idp.setSignatureAlgorithm(SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_ALGORITHM_ATTR));
boolean signaturesRequired = getBooleanAttributeValue(startElement, ConfigXmlConstants.SIGNATURES_REQUIRED_ATTR);
idp.setSignatureCanonicalizationMethod(getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_CANONICALIZATION_METHOD_ATTR));
idp.setSignatureAlgorithm(getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_ALGORITHM_ATTR));
while (xmlEventReader.hasNext()) {
XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
if (xmlEvent == null)
@@ -75,6 +79,10 @@ public class IDPXmlParser extends AbstractParser {
IDP.SingleLogoutService slo = parseSingleLogoutService(xmlEventReader, signaturesRequired);
idp.setSingleLogoutService(slo);
} else if (tag.equals(ConfigXmlConstants.HTTP_CLIENT_ELEMENT)) {
HttpClientConfig config = parseHttpClientElement(xmlEventReader);
idp.setHttpClientConfig(config);
} else if (tag.equals(ConfigXmlConstants.KEYS_ELEMENT)) {
KeysXmlParser parser = new KeysXmlParser();
List<Key> keys = (List<Key>)parser.parse(xmlEventReader);
@@ -90,29 +98,63 @@ public class IDPXmlParser extends AbstractParser {
protected IDP.SingleLogoutService parseSingleLogoutService(XMLEventReader xmlEventReader, boolean signaturesRequired) throws ParsingException {
IDP.SingleLogoutService slo = new IDP.SingleLogoutService();
StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
slo.setSignRequest(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
slo.setValidateResponseSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
slo.setValidateRequestSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_REQUEST_SIGNATURE_ATTR, signaturesRequired));
slo.setRequestBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
slo.setResponseBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
slo.setSignResponse(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_RESPONSE_ATTR, signaturesRequired));
slo.setPostBindingUrl(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.POST_BINDING_URL_ATTR));
slo.setRedirectBindingUrl(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.REDIRECT_BINDING_URL_ATTR));
slo.setSignRequest(getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
slo.setValidateResponseSignature(getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
slo.setValidateRequestSignature(getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_REQUEST_SIGNATURE_ATTR, signaturesRequired));
slo.setRequestBinding(getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
slo.setResponseBinding(getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
slo.setSignResponse(getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_RESPONSE_ATTR, signaturesRequired));
slo.setPostBindingUrl(getAttributeValue(element, ConfigXmlConstants.POST_BINDING_URL_ATTR));
slo.setRedirectBindingUrl(getAttributeValue(element, ConfigXmlConstants.REDIRECT_BINDING_URL_ATTR));
return slo;
}
protected IDP.SingleSignOnService parseSingleSignOnService(XMLEventReader xmlEventReader, boolean signaturesRequired) throws ParsingException {
IDP.SingleSignOnService sso = new IDP.SingleSignOnService();
StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
sso.setSignRequest(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
sso.setValidateResponseSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
sso.setValidateAssertionSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_ASSERTION_SIGNATURE_ATTR));
sso.setRequestBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
sso.setResponseBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
sso.setBindingUrl(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.BINDING_URL_ATTR));
sso.setSignRequest(getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
sso.setValidateResponseSignature(getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
sso.setValidateAssertionSignature(getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_ASSERTION_SIGNATURE_ATTR));
sso.setRequestBinding(getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
sso.setResponseBinding(getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
sso.setBindingUrl(getAttributeValue(element, ConfigXmlConstants.BINDING_URL_ATTR));
return sso;
}
private HttpClientConfig parseHttpClientElement(XMLEventReader xmlEventReader) throws ParsingException {
StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
StaxParserUtil.validate(startElement, ConfigXmlConstants.HTTP_CLIENT_ELEMENT);
HttpClientConfig config = new HttpClientConfig();
config.setAllowAnyHostname(getBooleanAttributeValue(startElement, ConfigXmlConstants.ALLOW_ANY_HOSTNAME_ATTR, false));
config.setClientKeystore(getAttributeValue(startElement, ConfigXmlConstants.CLIENT_KEYSTORE_ATTR));
config.setClientKeystorePassword(getAttributeValue(startElement, ConfigXmlConstants.CLIENT_KEYSTORE_PASSWORD_ATTR));
config.setConnectionPoolSize(getIntegerAttributeValue(startElement, ConfigXmlConstants.CONNECTION_POOL_SIZE_ATTR, 0));
config.setDisableTrustManager(getBooleanAttributeValue(startElement, ConfigXmlConstants.ALLOW_ANY_HOSTNAME_ATTR, false));
config.setProxyUrl(getAttributeValue(startElement, ConfigXmlConstants.PROXY_URL_ATTR));
config.setTruststore(getAttributeValue(startElement, ConfigXmlConstants.TRUSTSTORE_ATTR));
config.setTruststorePassword(getAttributeValue(startElement, ConfigXmlConstants.TRUSTSTORE_PASSWORD_ATTR));
while (xmlEventReader.hasNext()) {
XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
if (xmlEvent == null)
break;
if (xmlEvent instanceof EndElement) {
EndElement endElement = (EndElement) StaxParserUtil.getNextEvent(xmlEventReader);
String endElementName = StaxParserUtil.getEndElementName(endElement);
if (endElementName.equals(ConfigXmlConstants.ROLE_IDENTIFIERS_ELEMENT))
break;
else
continue;
}
String tag = StaxParserUtil.getStartElementName(startElement);
StaxParserUtil.bypassElementBlock(xmlEventReader, tag);
}
return config;
}
@Override
public boolean supports(QName qname) {
return false;
@@ -48,6 +48,13 @@ public class SPXmlParser extends AbstractParser {
return str;
}
public static int getIntegerAttributeValue(StartElement startElement, String tag, int defaultValue) {
String result = getAttributeValue(startElement, tag);
if (result == null)
return defaultValue;
return Integer.valueOf(result);
}
public static boolean getBooleanAttributeValue(StartElement startElement, String tag, boolean defaultValue) {
String result = getAttributeValue(startElement, tag);
if (result == null)
@@ -0,0 +1,101 @@
/*
* 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.adapters.saml.descriptor.parsers;
import java.io.IOException;
import java.io.InputStream;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.dom.DOMStructure;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.saml.common.constants.JBossSAMLConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.exceptions.ParsingException;
import org.keycloak.saml.processing.core.util.NamespaceContext;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* Goes through the given XML file and extracts names, certificates and keys from the KeyInfo elements.
* @author hmlnarik
*/
public class SamlDescriptorIDPKeysExtractor {
private static final NamespaceContext NS_CONTEXT = new NamespaceContext();
static {
NS_CONTEXT.addNsUriPair("m", JBossSAMLURIConstants.METADATA_NSURI.get());
NS_CONTEXT.addNsUriPair("dsig", JBossSAMLURIConstants.XMLDSIG_NSURI.get());
}
private final KeyInfoFactory kif = KeyInfoFactory.getInstance();
private final XPathFactory xPathfactory = XPathFactory.newInstance();
private final XPath xpath = xPathfactory.newXPath();
{
xpath.setNamespaceContext(NS_CONTEXT);
}
public MultivaluedHashMap<String, KeyInfo> parse(InputStream stream) throws ParsingException {
MultivaluedHashMap<String, KeyInfo> res = new MultivaluedHashMap<>();
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(stream);
XPathExpression expr = xpath.compile("/m:EntitiesDescriptor/m:EntityDescriptor/m:IDPSSODescriptor/m:KeyDescriptor");
NodeList keyDescriptors = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
for (int i = 0; i < keyDescriptors.getLength(); i ++) {
Node keyDescriptor = keyDescriptors.item(i);
Element keyDescriptorEl = (Element) keyDescriptor;
KeyInfo ki = processKeyDescriptor(keyDescriptorEl);
if (ki != null) {
String use = keyDescriptorEl.getAttribute(JBossSAMLConstants.USE.get());
res.add(use, ki);
}
}
} catch (SAXException | IOException | ParserConfigurationException | MarshalException | XPathExpressionException e) {
throw new ParsingException("Error parsing SAML descriptor", e);
}
return res;
}
private KeyInfo processKeyDescriptor(Element keyDescriptor) throws MarshalException {
NodeList childNodes = keyDescriptor.getElementsByTagNameNS(JBossSAMLURIConstants.XMLDSIG_NSURI.get(), JBossSAMLConstants.KEY_INFO.get());
if (childNodes.getLength() == 0) {
return null;
}
Node keyInfoNode = childNodes.item(0);
return (keyInfoNode == null) ? null : kif.unmarshalKeyInfo(new DOMStructure(keyInfoNode));
}
}
@@ -64,11 +64,20 @@ import org.w3c.dom.Node;
import java.io.IOException;
import java.net.URI;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyManagementException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
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.w3c.dom.Element;
/**
*
@@ -257,13 +266,44 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
}
private void validateSamlSignature(SAMLDocumentHolder holder, boolean postBinding, String paramKey) throws VerificationException {
KeyLocator signatureValidationKey = deployment.getIDP().getSignatureValidationKeyLocator();
if (postBinding) {
verifyPostBindingSignature(holder.getSamlDocument(), deployment.getIDP().getSignatureValidationKey());
verifyPostBindingSignature(holder.getSamlDocument(), signatureValidationKey);
} else {
verifyRedirectBindingSignature(deployment.getIDP().getSignatureValidationKey(), paramKey);
String keyId = getMessageSigningKeyId(holder.getSamlObject());
verifyRedirectBindingSignature(paramKey, signatureValidationKey, keyId);
}
}
private String getMessageSigningKeyId(SAML2Object doc) {
final ExtensionsType extensions;
if (doc instanceof RequestAbstractType) {
extensions = ((RequestAbstractType) doc).getExtensions();
} else if (doc instanceof StatusResponseType) {
extensions = ((StatusResponseType) doc).getExtensions();
} else {
return null;
}
if (extensions == null) {
return null;
}
for (Object ext : extensions.getAny()) {
if (! (ext instanceof Element)) {
continue;
}
String res = KeycloakKeySamlExtensionGenerator.getMessageSigningKeyIdFromElement((Element) ext);
if (res != null) {
return res;
}
}
return null;
}
private boolean checkStatusCodeValue(StatusCodeType statusCode, String expectedValue){
if(statusCode != null && statusCode.getValue()!=null){
String v = statusCode.getValue().toString();
@@ -473,10 +513,10 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
return false;
}
public void verifyPostBindingSignature(Document document, PublicKey publicKey) throws VerificationException {
public void verifyPostBindingSignature(Document document, KeyLocator keyLocator) throws VerificationException {
SAML2Signature saml2Signature = new SAML2Signature();
try {
if (!saml2Signature.validate(document, publicKey)) {
if (!saml2Signature.validate(document, keyLocator)) {
throw new VerificationException("Invalid signature on document");
}
} catch (ProcessingException e) {
@@ -484,7 +524,7 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
}
}
public void verifyRedirectBindingSignature(PublicKey publicKey, String paramKey) throws VerificationException {
private void verifyRedirectBindingSignature(String paramKey, KeyLocator keyLocator, String keyId) throws VerificationException {
String request = facade.getRequest().getQueryParamValue(paramKey);
String algorithm = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
String signature = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
@@ -511,16 +551,80 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
try {
//byte[] decodedSignature = RedirectBindingUtil.urlBase64Decode(signature);
byte[] decodedSignature = Base64.decode(signature);
byte[] rawQueryBytes = rawQuery.getBytes("UTF-8");
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.getFromXmlMethod(decodedAlgorithm);
Signature validator = signatureAlgorithm.createSignature(); // todo plugin signature alg
validator.initVerify(publicKey);
validator.update(rawQuery.getBytes("UTF-8"));
if (!validator.verify(decodedSignature)) {
if (! validateRedirectBindingSignature(signatureAlgorithm, rawQueryBytes, decodedSignature, keyLocator, keyId)) {
throw new VerificationException("Invalid query param signature");
}
} catch (Exception e) {
throw new VerificationException(e);
}
}
private boolean validateRedirectBindingSignature(SignatureAlgorithm sigAlg, byte[] rawQueryBytes, byte[] decodedSignature, KeyLocator locator, String keyId)
throws KeyManagementException, VerificationException {
try {
Key key;
try {
key = locator.getKey(keyId);
boolean keyLocated = key != null;
if (validateRedirectBindingSignatureForKey(sigAlg, rawQueryBytes, decodedSignature, key)) {
return true;
}
if (keyLocated) {
return false;
}
} catch (KeyManagementException ex) {
}
} catch (SignatureException ex) {
log.debug("Verification failed for key %s: %s", keyId, ex);
log.trace(ex);
}
if (locator instanceof Iterable) {
Iterable<Key> availableKeys = (Iterable<Key>) locator;
log.trace("Trying hard to validate XML signature using all available keys.");
for (Key key : availableKeys) {
try {
if (validateRedirectBindingSignatureForKey(sigAlg, rawQueryBytes, decodedSignature, key)) {
return true;
}
} catch (SignatureException ex) {
log.debug("Verification failed: %s", ex);
}
}
}
return false;
}
private boolean validateRedirectBindingSignatureForKey(SignatureAlgorithm sigAlg, byte[] rawQueryBytes, byte[] decodedSignature, Key key)
throws SignatureException {
if (key == null) {
return false;
}
if (! (key instanceof PublicKey)) {
log.warnf("Unusable key for signature validation: %s", key);
return false;
}
Signature signature = sigAlg.createSignature(); // todo plugin signature alg
try {
signature.initVerify((PublicKey) key);
} catch (InvalidKeyException ex) {
log.warnf(ex, "Unusable key for signature validation: %s", key);
return false;
}
signature.update(rawQueryBytes);
return signature.verify(decodedSignature);
}
}
@@ -82,8 +82,10 @@ public class WebBrowserSsoAuthenticationHandler extends AbstractSamlAuthenticati
if (deployment.getSignatureCanonicalizationMethod() != null)
binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
binding.signatureAlgorithm(deployment.getSignatureAlgorithm())
.signWith(deployment.getSigningKeyPair())
.signWith(null, deployment.getSigningKeyPair())
.signDocument();
// TODO: As part of KEYCLOAK-3810, add KeyID to the SAML document
// <related DocumentBuilder>.addExtension(new KeycloakKeySamlExtensionGenerator(<key ID>));
}
@@ -113,8 +115,10 @@ public class WebBrowserSsoAuthenticationHandler extends AbstractSamlAuthenticati
if (deployment.getSignatureCanonicalizationMethod() != null)
binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
binding.signatureAlgorithm(deployment.getSignatureAlgorithm());
binding.signWith(deployment.getSigningKeyPair())
binding.signWith(null, deployment.getSigningKeyPair())
.signDocument();
// TODO: As part of KEYCLOAK-3810, add KeyID to the SAML document
// <related DocumentBuilder>.addExtension(new KeycloakKeySamlExtensionGenerator(<key ID>));
}
binding.relayState("logout");
@@ -0,0 +1,175 @@
/*
* 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.adapters.saml.rotation;
import java.security.Key;
import java.security.KeyManagementException;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyName;
import org.apache.http.client.HttpClient;
import org.jboss.logging.Logger;
import org.keycloak.adapters.cloned.HttpAdapterUtils;
import org.keycloak.adapters.cloned.HttpClientAdapterException;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.Time;
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
import org.keycloak.rotation.KeyLocator;
import org.keycloak.saml.processing.api.util.KeyInfoTools;
/**
* This class defines a {@link KeyLocator} that looks up public keys and certificates in IdP's
* SAML descriptor (i.e. http://{host}/auth/realms/{realm}/protocol/saml/descriptor).
*
* Based on {@code JWKPublicKeyLocator}.
*
* @author hmlnarik
*/
public class SamlDescriptorPublicKeyLocator implements KeyLocator, Iterable<PublicKey> {
private static final Logger LOG = Logger.getLogger(SamlDescriptorPublicKeyLocator.class);
/**
* Time between two subsequent requests (in seconds).
*/
private final int minTimeBetweenDescriptorRequests;
/**
* Time to live for cache entries (in seconds).
*/
private final int cacheEntryTtl;
/**
* Target descriptor URL.
*/
private final String descriptorUrl;
private final Map<String, PublicKey> publicKeyCache = new ConcurrentHashMap<>();
private final HttpClient client;
private volatile int lastRequestTime = 0;
public SamlDescriptorPublicKeyLocator(String descriptorUrl, int minTimeBetweenDescriptorRequests, int cacheEntryTtl, HttpClient httpClient) {
this.minTimeBetweenDescriptorRequests = minTimeBetweenDescriptorRequests <= 0
? 20
: minTimeBetweenDescriptorRequests;
this.descriptorUrl = descriptorUrl;
this.cacheEntryTtl = cacheEntryTtl;
this.client = httpClient;
}
@Override
public Key getKey(String kid) throws KeyManagementException {
if (kid == null) {
LOG.debugf("Invalid key id: %s", kid);
return null;
}
LOG.tracef("Requested key id: %s", kid);
int currentTime = Time.currentTime();
PublicKey res;
if (currentTime > this.lastRequestTime + this.cacheEntryTtl) {
LOG.debugf("Performing regular cache cleanup.");
res = refreshCertificateCacheAndGet(kid);
} else {
res = publicKeyCache.get(kid);
if (res == null) {
if (currentTime > this.lastRequestTime + this.minTimeBetweenDescriptorRequests) {
res = refreshCertificateCacheAndGet(kid);
} else {
LOG.debugf("Won't send request to realm SAML descriptor url, timeout not expired. Last request time was %d", lastRequestTime);
}
}
}
return res;
}
@Override
public synchronized void refreshKeyCache() {
LOG.info("Forcing key cache cleanup and refresh.");
this.publicKeyCache.clear();
refreshCertificateCacheAndGet(null);
}
private synchronized PublicKey refreshCertificateCacheAndGet(String kid) {
if (this.descriptorUrl == null) {
return null;
}
this.lastRequestTime = Time.currentTime();
LOG.debugf("Refreshing public key cache from %s", this.descriptorUrl);
List<KeyInfo> signingCerts;
try {
MultivaluedHashMap<String, KeyInfo> certs = HttpAdapterUtils.downloadKeysFromSamlDescriptor(client, this.descriptorUrl);
signingCerts = certs.get(KeyTypes.SIGNING.value());
} catch (HttpClientAdapterException ex) {
LOG.error("Could not refresh certificates from the server", ex);
return null;
}
if (signingCerts == null) {
return null;
}
LOG.debugf("Certificates retrieved from server, filling public key cache");
// Only clear cache after it is certain that the SAML descriptor has been read successfully
this.publicKeyCache.clear();
for (KeyInfo ki : signingCerts) {
KeyName keyName = KeyInfoTools.getKeyName(ki);
X509Certificate x509certificate = KeyInfoTools.getX509Certificate(ki);
if (x509certificate != null && keyName != null) {
LOG.tracef("Registering signing certificate %s", keyName.getName());
this.publicKeyCache.put(keyName.getName(), x509certificate.getPublicKey());
} else {
LOG.tracef("Ignoring certificate %s: %s", keyName, x509certificate);
}
}
return (kid == null ? null : this.publicKeyCache.get(kid));
}
@Override
public String toString() {
return "Keys retrieved from SAML descriptor at " + descriptorUrl;
}
@Override
public Iterator<PublicKey> iterator() {
if (this.publicKeyCache.isEmpty()) {
refreshCertificateCacheAndGet(null);
}
return this.publicKeyCache.values().iterator();
}
}
@@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<xs:schema version="1.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="urn:keycloak:saml:adapter"
targetNamespace="urn:keycloak:saml:adapter"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xs:element name="keycloak-saml-adapter" type="adapter-type"/>
<xs:complexType name="adapter-type">
<xs:annotation>
<xs:documentation>
<![CDATA[
The Keycloak SAML Adapter keycloak-saml.xml config file
]]>
</xs:documentation>
</xs:annotation>
<xs:all>
<xs:element name="SP" maxOccurs="1" minOccurs="0" type="sp-type"/>
</xs:all>
</xs:complexType>
<xs:complexType name="sp-type">
<xs:all>
<xs:element name="Keys" type="keys-type" minOccurs="0" maxOccurs="1"/>
<xs:element name="PrincipalNameMapping" type="principal-name-mapping-type" minOccurs="0" maxOccurs="1"/>
<xs:element name="RoleIdentifiers" type="role-identifiers-type" minOccurs="0" maxOccurs="1"/>
<xs:element name="IDP" type="idp-type" minOccurs="1" maxOccurs="1"/>
</xs:all>
<xs:attribute name="entityID" type="xs:string" use="required"/>
<xs:attribute name="sslPolicy" type="xs:string" use="optional"/>
<xs:attribute name="nameIDPolicyFormat" type="xs:string" use="optional"/>
<xs:attribute name="logoutPage" type="xs:string" use="optional"/>
<xs:attribute name="forceAuthentication" type="xs:boolean" use="optional"/>
<xs:attribute name="isPassive" type="xs:boolean" use="optional"/>
<xs:attribute name="turnOffChangeSessionIdOnLogin" type="xs:boolean" use="optional"/>
</xs:complexType>
<xs:complexType name="keys-type">
<xs:sequence>
<xs:element name="Key" type="key-type" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="key-type">
<xs:all>
<xs:element name="KeyStore" maxOccurs="1" minOccurs="0" type="key-store-type"/>
<xs:element name="PrivateKeyPem" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="PublicKeyPem" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="CertificatePem" type="xs:string" minOccurs="0" maxOccurs="1"/>
</xs:all>
<xs:attribute name="signing" type="xs:boolean" use="optional"/>
<xs:attribute name="encryption" type="xs:boolean" use="optional"/>
</xs:complexType>
<xs:complexType name="key-store-type">
<xs:all>
<xs:element name="PrivateKey" maxOccurs="1" minOccurs="0" type="private-key-type"/>
<xs:element name="Certificate" type="certificate-type" minOccurs="0" maxOccurs="1"/>
</xs:all>
<xs:attribute name="file" type="xs:string" use="optional"/>
<xs:attribute name="resource" type="xs:string" use="optional"/>
<xs:attribute name="password" type="xs:string" use="required"/>
</xs:complexType>
<xs:complexType name="private-key-type">
<xs:attribute name="alias" type="xs:string" use="required"/>
<xs:attribute name="password" type="xs:string" use="required"/>
</xs:complexType>
<xs:complexType name="certificate-type">
<xs:attribute name="alias" type="xs:string" use="required"/>
</xs:complexType>
<xs:complexType name="principal-name-mapping-type">
<xs:attribute name="policy" type="xs:string" use="required"/>
<xs:attribute name="attribute" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="role-identifiers-type">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="Attribute" maxOccurs="unbounded" minOccurs="0" type="attribute-type"/>
</xs:choice>
</xs:complexType>
<xs:complexType name="attribute-type">
<xs:attribute name="name" type="xs:string" use="required"/>
</xs:complexType>
<xs:complexType name="idp-type">
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:element name="SingleSignOnService" maxOccurs="1" minOccurs="1" type="sign-on-type"/>
<xs:element name="SingleLogoutService" type="logout-type" minOccurs="0" maxOccurs="1"/>
<xs:element name="Keys" type="keys-type" minOccurs="0" maxOccurs="1"/>
<xs:element name="HttpClient" type="http-client-type" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
<xs:attribute name="entityID" type="xs:string" use="required"/>
<xs:attribute name="signaturesRequired" type="xs:boolean" use="required"/>
<xs:attribute name="signatureAlgorithm" type="xs:string" use="optional"/>
<xs:attribute name="signatureCanonicalizationMethod" type="xs:string" use="optional"/>
<xs:attribute name="encryption" type="xs:boolean" use="optional"/>
</xs:complexType>
<xs:complexType name="sign-on-type">
<xs:attribute name="signRequest" type="xs:boolean" use="optional"/>
<xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional"/>
<xs:attribute name="validateAssertionSignature" type="xs:boolean" use="optional"/>
<xs:attribute name="requestBinding" type="xs:string" use="optional"/>
<xs:attribute name="responseBinding" type="xs:string" use="optional"/>
<xs:attribute name="bindingUrl" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="logout-type">
<xs:attribute name="signRequest" type="xs:boolean" use="optional"/>
<xs:attribute name="signResponse" type="xs:boolean" use="optional"/>
<xs:attribute name="validateRequestSignature" type="xs:boolean" use="optional"/>
<xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional"/>
<xs:attribute name="requestBinding" type="xs:string" use="optional"/>
<xs:attribute name="responseBinding" type="xs:string" use="optional"/>
<xs:attribute name="postBindingUrl" type="xs:string" use="optional"/>
<xs:attribute name="redirectBindingUrl" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="http-client-type">
<xs:attribute name="allowAnyHostname" type="xs:boolean" use="optional"/>
<xs:attribute name="clientKeystore" type="xs:string" use="optional"/>
<xs:attribute name="clientKeystorePassword" type="xs:string" use="optional"/>
<xs:attribute name="connectionPoolSize" type="xs:int" use="optional"/>
<xs:attribute name="disableTrustManager" type="xs:boolean" use="optional"/>
<xs:attribute name="proxyUrl" type="xs:string" use="optional"/>
<xs:attribute name="truststore" type="xs:string" use="optional"/>
<xs:attribute name="truststorePassword" type="xs:string" use="optional"/>
</xs:complexType>
</xs:schema>
@@ -0,0 +1,82 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.keycloak.adapters.cloned;
import java.io.InputStream;
import java.security.cert.X509Certificate;
import java.util.List;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyName;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import static org.hamcrest.CoreMatchers.*;
import org.junit.Test;
import static org.junit.Assert.*;
import org.keycloak.adapters.saml.config.parsers.ConfigXmlConstants;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
import org.keycloak.saml.common.exceptions.ParsingException;
/**
*
* @author hmlnarik
*/
public class HttpAdapterUtilsTest {
private <T> T getContent(List<Object> objects, Class<T> clazz) {
for (Object o : objects) {
if (clazz.isInstance(o)) {
return (T) o;
}
}
return null;
}
@Test
public void testExtractKeysFromSamlDescriptor() throws ParsingException {
InputStream xmlStream = HttpAdapterUtilsTest.class.getResourceAsStream("saml-descriptor-valid.xml");
MultivaluedHashMap<String, KeyInfo> res = HttpAdapterUtils.extractKeysFromSamlDescriptor(xmlStream);
assertThat(res, notNullValue());
assertThat(res.keySet(), hasItems(KeyTypes.SIGNING.value()));
assertThat(res.get(ConfigXmlConstants.SIGNING_ATTR), notNullValue());
assertThat(res.get(ConfigXmlConstants.SIGNING_ATTR).size(), equalTo(2));
KeyInfo ki;
KeyName keyName;
X509Data x509data;
X509Certificate x509certificate;
ki = res.get(ConfigXmlConstants.SIGNING_ATTR).get(0);
assertThat(ki.getContent().size(), equalTo(2));
assertThat((List<Object>) ki.getContent(), hasItem(instanceOf(X509Data.class)));
assertThat((List<Object>) ki.getContent(), hasItem(instanceOf(KeyName.class)));
keyName = getContent(ki.getContent(), KeyName.class);
assertThat(keyName.getName(), equalTo("rJkJlvowmv1Id74GznieaAC5jU5QQp_ILzuG-GsweTI"));
x509data = getContent(ki.getContent(), X509Data.class);
assertThat(x509data, notNullValue());
x509certificate = getContent(x509data.getContent(), X509Certificate.class);
assertThat(x509certificate, notNullValue());
assertThat(x509certificate.getSigAlgName(), equalTo("SHA256withRSA"));
ki = res.get(ConfigXmlConstants.SIGNING_ATTR).get(1);
assertThat(ki.getContent().size(), equalTo(2));
assertThat((List<Object>) ki.getContent(), hasItem(instanceOf(X509Data.class)));
assertThat((List<Object>) ki.getContent(), hasItem(instanceOf(KeyName.class)));
keyName = getContent(ki.getContent(), KeyName.class);
assertThat(keyName.getName(), equalTo("BzYc4GwL8HVrAhNyNdp-lTah2DvU9jU03kby9Ynohr4"));
x509data = getContent(ki.getContent(), X509Data.class);
assertThat(x509data, notNullValue());
x509certificate = getContent(x509data.getContent(), X509Certificate.class);
assertThat(x509certificate, notNullValue());
assertThat(x509certificate.getSigAlgName(), equalTo("SHA256withRSA"));
}
}
@@ -0,0 +1,180 @@
/*
* 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.adapters.saml.config.parsers;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import org.junit.Test;
import org.keycloak.adapters.saml.config.IDP;
import org.keycloak.adapters.saml.config.Key;
import org.keycloak.adapters.saml.config.KeycloakSamlAdapter;
import org.keycloak.adapters.saml.config.SP;
import org.keycloak.saml.common.util.StaxParserUtil;
import java.io.InputStream;
import org.junit.Rule;
import org.junit.rules.ExpectedException;
import org.keycloak.saml.common.exceptions.ParsingException;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class KeycloakSamlAdapterXMLParserTest {
private static final String CURRENT_XSD_LOCATION = "/schema/keycloak_saml_adapter_1_7.xsd";
@Rule
public ExpectedException expectedException = ExpectedException.none();
private void testValidationValid(String fileName) throws Exception {
InputStream schema = getClass().getResourceAsStream(CURRENT_XSD_LOCATION);
InputStream is = getClass().getResourceAsStream(fileName);
assertNotNull(is);
assertNotNull(schema);
StaxParserUtil.validate(is, schema);
}
@Test
public void testValidationSimpleFile() throws Exception {
testValidationValid("keycloak-saml.xml");
}
@Test
public void testValidationMultipleKeys() throws Exception {
testValidationValid("keycloak-saml-multiple-signing-keys.xml");
}
@Test
public void testValidationWithHttpClient() throws Exception {
testValidationValid("keycloak-saml-wth-http-client-settings.xml");
}
@Test
public void testValidationKeyInvalid() throws Exception {
InputStream schemaIs = KeycloakSamlAdapterXMLParser.class.getResourceAsStream(CURRENT_XSD_LOCATION);
InputStream is = getClass().getResourceAsStream("keycloak-saml-invalid.xml");
assertNotNull(is);
assertNotNull(schemaIs);
expectedException.expect(ParsingException.class);
StaxParserUtil.validate(is, schemaIs);
}
@Test
public void testXmlParser() throws Exception {
InputStream is = getClass().getResourceAsStream("keycloak-saml.xml");
assertNotNull(is);
KeycloakSamlAdapterXMLParser parser = new KeycloakSamlAdapterXMLParser();
KeycloakSamlAdapter config = (KeycloakSamlAdapter)parser.parse(is);
assertNotNull(config);
assertEquals(1, config.getSps().size());
SP sp = config.getSps().get(0);
assertEquals("sp", sp.getEntityID());
assertEquals("ssl", sp.getSslPolicy());
assertEquals("format", sp.getNameIDPolicyFormat());
assertTrue(sp.isForceAuthentication());
assertTrue(sp.isIsPassive());
assertEquals(2, sp.getKeys().size());
Key signing = sp.getKeys().get(0);
assertTrue(signing.isSigning());
Key.KeyStoreConfig keystore = signing.getKeystore();
assertNotNull(keystore);
assertEquals("file", keystore.getFile());
assertEquals("cp", keystore.getResource());
assertEquals("pw", keystore.getPassword());
assertEquals("private alias", keystore.getPrivateKeyAlias());
assertEquals("private pw", keystore.getPrivateKeyPassword());
assertEquals("cert alias", keystore.getCertificateAlias());
Key encryption = sp.getKeys().get(1);
assertTrue(encryption.isEncryption());
assertEquals("private pem", encryption.getPrivateKeyPem());
assertEquals("public pem", encryption.getPublicKeyPem());
assertEquals("policy", sp.getPrincipalNameMapping().getPolicy());
assertEquals("attribute", sp.getPrincipalNameMapping().getAttributeName());
assertTrue(sp.getRoleAttributes().size() == 1);
assertTrue(sp.getRoleAttributes().contains("member"));
IDP idp = sp.getIdp();
assertEquals("idp", idp.getEntityID());
assertEquals("RSA", idp.getSignatureAlgorithm());
assertEquals("canon", idp.getSignatureCanonicalizationMethod());
assertTrue(idp.getSingleSignOnService().isSignRequest());
assertTrue(idp.getSingleSignOnService().isValidateResponseSignature());
assertEquals("post", idp.getSingleSignOnService().getRequestBinding());
assertEquals("url", idp.getSingleSignOnService().getBindingUrl());
assertTrue(idp.getSingleLogoutService().isSignRequest());
assertTrue(idp.getSingleLogoutService().isSignResponse());
assertTrue(idp.getSingleLogoutService().isValidateRequestSignature());
assertTrue(idp.getSingleLogoutService().isValidateResponseSignature());
assertEquals("redirect", idp.getSingleLogoutService().getRequestBinding());
assertEquals("post", idp.getSingleLogoutService().getResponseBinding());
assertEquals("posturl", idp.getSingleLogoutService().getPostBindingUrl());
assertEquals("redirecturl", idp.getSingleLogoutService().getRedirectBindingUrl());
assertTrue(idp.getKeys().size() == 1);
assertTrue(idp.getKeys().get(0).isSigning());
assertEquals("cert pem", idp.getKeys().get(0).getCertificatePem());
}
@Test
public void testXmlParserMultipleSigningKeys() throws Exception {
InputStream is = getClass().getResourceAsStream("keycloak-saml-multiple-signing-keys.xml");
assertNotNull(is);
KeycloakSamlAdapterXMLParser parser = new KeycloakSamlAdapterXMLParser();
KeycloakSamlAdapter config = (KeycloakSamlAdapter) parser.parse(is);
assertNotNull(config);
assertEquals(1, config.getSps().size());
SP sp = config.getSps().get(0);
IDP idp = sp.getIdp();
assertTrue(idp.getKeys().size() == 4);
for (int i = 0; i < 4; i ++) {
Key key = idp.getKeys().get(i);
assertTrue(key.isSigning());
assertEquals("cert pem " + i, idp.getKeys().get(i).getCertificatePem());
}
}
@Test
public void testXmlParserHttpClientSettings() throws Exception {
InputStream is = getClass().getResourceAsStream("keycloak-saml-wth-http-client-settings.xml");
assertNotNull(is);
KeycloakSamlAdapterXMLParser parser = new KeycloakSamlAdapterXMLParser();
KeycloakSamlAdapter config = (KeycloakSamlAdapter) parser.parse(is);
assertNotNull(config);
assertEquals(1, config.getSps().size());
SP sp = config.getSps().get(0);
IDP idp = sp.getIdp();
assertThat(idp.getHttpClientConfig(), notNullValue());
assertThat(idp.getHttpClientConfig().getClientKeystore(), is("ks"));
assertThat(idp.getHttpClientConfig().getClientKeystorePassword(), is("ks-pwd"));
assertThat(idp.getHttpClientConfig().getProxyUrl(), is("pu"));
assertThat(idp.getHttpClientConfig().getTruststore(), is("ts"));
assertThat(idp.getHttpClientConfig().getTruststorePassword(), is("tsp"));
assertThat(idp.getHttpClientConfig().getConnectionPoolSize(), is(42));
assertThat(idp.getHttpClientConfig().isAllowAnyHostname(), is(true));
assertThat(idp.getHttpClientConfig().isDisableTrustManager(), is(true));
}
}
@@ -1,133 +0,0 @@
/*
* 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.test.adapters.saml;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.adapters.saml.config.IDP;
import org.keycloak.adapters.saml.config.Key;
import org.keycloak.adapters.saml.config.KeycloakSamlAdapter;
import org.keycloak.adapters.saml.config.SP;
import org.keycloak.adapters.saml.config.parsers.KeycloakSamlAdapterXMLParser;
import org.keycloak.saml.common.util.StaxParserUtil;
import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.InputStream;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class XmlParserTest {
@Test
public void testValidation() throws Exception {
{
InputStream schema = KeycloakSamlAdapterXMLParser.class.getResourceAsStream("/schema/keycloak_saml_adapter_1_6.xsd");
InputStream is = getClass().getResourceAsStream("/keycloak-saml.xml");
Assert.assertNotNull(is);
Assert.assertNotNull(schema);
StaxParserUtil.validate(is, schema);
}
{
InputStream sch = KeycloakSamlAdapterXMLParser.class.getResourceAsStream("/schema/keycloak_saml_adapter_1_6.xsd");
InputStream doc = getClass().getResourceAsStream("/keycloak-saml2.xml");
Assert.assertNotNull(doc);
Assert.assertNotNull(sch);
try {
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = factory.newSchema(new StreamSource(sch));
Validator validator = schema.newValidator();
StreamSource source = new StreamSource(doc);
source.setSystemId("/keycloak-saml2.xml");
validator.validate(source);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Test
public void testXmlParser() throws Exception {
InputStream is = getClass().getResourceAsStream("/keycloak-saml.xml");
Assert.assertNotNull(is);
KeycloakSamlAdapterXMLParser parser = new KeycloakSamlAdapterXMLParser();
KeycloakSamlAdapter config = (KeycloakSamlAdapter)parser.parse(is);
Assert.assertNotNull(config);
Assert.assertEquals(1, config.getSps().size());
SP sp = config.getSps().get(0);
Assert.assertEquals("sp", sp.getEntityID());
Assert.assertEquals("ssl", sp.getSslPolicy());
Assert.assertEquals("format", sp.getNameIDPolicyFormat());
Assert.assertTrue(sp.isForceAuthentication());
Assert.assertTrue(sp.isIsPassive());
Assert.assertEquals(2, sp.getKeys().size());
Key signing = sp.getKeys().get(0);
Assert.assertTrue(signing.isSigning());
Key.KeyStoreConfig keystore = signing.getKeystore();
Assert.assertNotNull(keystore);
Assert.assertEquals("file", keystore.getFile());
Assert.assertEquals("cp", keystore.getResource());
Assert.assertEquals("pw", keystore.getPassword());
Assert.assertEquals("private alias", keystore.getPrivateKeyAlias());
Assert.assertEquals("private pw", keystore.getPrivateKeyPassword());
Assert.assertEquals("cert alias", keystore.getCertificateAlias());
Key encryption = sp.getKeys().get(1);
Assert.assertTrue(encryption.isEncryption());
Assert.assertEquals("private pem", encryption.getPrivateKeyPem());
Assert.assertEquals("public pem", encryption.getPublicKeyPem());
Assert.assertEquals("policy", sp.getPrincipalNameMapping().getPolicy());
Assert.assertEquals("attribute", sp.getPrincipalNameMapping().getAttributeName());
Assert.assertTrue(sp.getRoleAttributes().size() == 1);
Assert.assertTrue(sp.getRoleAttributes().contains("member"));
IDP idp = sp.getIdp();
Assert.assertEquals("idp", idp.getEntityID());
Assert.assertEquals("RSA", idp.getSignatureAlgorithm());
Assert.assertEquals("canon", idp.getSignatureCanonicalizationMethod());
Assert.assertTrue(idp.getSingleSignOnService().isSignRequest());
Assert.assertTrue(idp.getSingleSignOnService().isValidateResponseSignature());
Assert.assertEquals("post", idp.getSingleSignOnService().getRequestBinding());
Assert.assertEquals("url", idp.getSingleSignOnService().getBindingUrl());
Assert.assertTrue(idp.getSingleLogoutService().isSignRequest());
Assert.assertTrue(idp.getSingleLogoutService().isSignResponse());
Assert.assertTrue(idp.getSingleLogoutService().isValidateRequestSignature());
Assert.assertTrue(idp.getSingleLogoutService().isValidateResponseSignature());
Assert.assertEquals("redirect", idp.getSingleLogoutService().getRequestBinding());
Assert.assertEquals("post", idp.getSingleLogoutService().getResponseBinding());
Assert.assertEquals("posturl", idp.getSingleLogoutService().getPostBindingUrl());
Assert.assertEquals("redirecturl", idp.getSingleLogoutService().getRedirectBindingUrl());
Assert.assertTrue(idp.getKeys().size() == 1);
Assert.assertTrue(idp.getKeys().get(0).isSigning());
Assert.assertEquals("cert pem", idp.getKeys().get(0).getCertificatePem());
}
}
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
~ 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.
-->
<EntitiesDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#" Name="urn:keycloak">
<EntityDescriptor entityID="http://localhost:8081/auth/realms/master">
<IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<dsig:KeyInfo>
<dsig:KeyName>rJkJlvowmv1Id74GznieaAC5jU5QQp_ILzuG-GsweTI</dsig:KeyName>
<dsig:X509Data>
<dsig:X509Certificate>
MIICmzCCAYMCBgFX/9ccIDANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMTYxMDI2MDcxMjUwWhcNMjYxMDI2MDgxNDMwWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPjDrM890OoFWLIU5xNT+v8B8EkpOGY1y/9Yi/yQd95uG/p5LaywiPsw+lPy4tSn1pH/2SxNDST2zynKPDd1lYDev43m0sC2FfD2H73q3udQRqSOxW1e8FrTrGDIHxb82UNrCPlu+fH+xYSkigrkOvLvPigTwSIcu8vgs0lk9FqJ81ty3Wj2e9lS7JJGAJ3pC7rp39VLdJSKbfyj/v2RYBeG5Pscncl8cjUOHUq5u19hThjkU2jOBzgIK2JS0bNmzSfH1eBTZMoCQBI1UJ1IbA8tqjQwpOXc+JkPBRU8T/JUQoQlSR6DTcPFvDgH2oGZYFHFfUontZqtz8jrIt2pxBAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAK5VgQp1x1FKgabFI6W/iGuy9ZCRoAixOOEGGkDps6dOEFgTQKTy5D/FZts9KuNxhhiD+NvS0d5BKYa5ITPLVPnGucgYkZhz+/+GhxmbjeQr0eJPaY7ZgLfH3tPA6tfdIkA0iE1En1sKEwt6R6DZjh9jtP9laoUoddTvYaFLJpZ2u1Ik94q6ZqX0fS/RKchaBHjhg6MtqCcHt07CBKHh8XNmKPXVSJC/p0MjyXv+qLaNNqyaAvAw6P6DX1hNjzrdkuaaHGXhu6kkezZUVlDWAm9cd1ppqalSK6ggy7yMW1NWTd/NYOPsFU2TS8DDPzRo14s1Qvw4v+TY6yT0NURJPQA=
</dsig:X509Certificate>
</dsig:X509Data>
</dsig:KeyInfo>
</KeyDescriptor>
<KeyDescriptor use="signing">
<dsig:KeyInfo>
<dsig:KeyName>BzYc4GwL8HVrAhNyNdp-lTah2DvU9jU03kby9Ynohr4</dsig:KeyName>
<dsig:X509Data>
<dsig:X509Certificate>
MIICmzCCAYMCBgFX/9eK7TANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMTYxMDI2MDcxMzE4WhcNMjYxMDI2MDgxNDU4WjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCDLT+40/BWzWPSVmpaSaZRs5lBMQ9VP9TCoXkby4PHqxIWRecTPM8fcNkPNPE/tiR2tUIpMXPDzgXNFA/EMoB3V1OEVXPecjKtiZczdR6pi75CBx7PJ2fSXg6xpjhZmHu0k7x591GZdP8Iiu2E6b9QA2p5VXgNgfuP07XzgabnSvIrLG60Imus3u6C2qA/QEuY7EYQWrFooriYLW6B8s3xU8R1a92SLMT8JsfMWXi+1CzAhIbVvdwUwkhVDDhAU6pUek88QQgxodd3FAMksoijCGFN1yrCkovlFhKb3j9AC6Icd9eeJuwYddN/nMeMGEDOeCcAGBACiaUisjUvZDw1AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAHAHbBI0CRfdw5ZHxHAjgSQvSj41c/4cfwln4Q7X3I5lMBbW3tcgond6Ku9eU46FzG5VpgXIgvEf4u0O9jUnxLlO50+t2SHwQ1RwHdBWQngVSZCRzscq3KrSzx1hx88qLyqcPrr3QtR92fYipDjENxttT/qJtDMrXlwLZEITlHDoneX319USYB9C4zlrCIsQ5XxQTTyCx886Pz15DSVSRxVp61HGk6ROsX/DG5/xwInlzgMZ0r3JWnAjtAaXqUrcwH9FXxco+xkiqKW79bGhWGQI9sXXvQSSNAaENMIUhxtd9uOi1l5e0EkKHE2fHlYyfdUDnFJWwSMXd/NM+hVI4Lw=
</dsig:X509Certificate>
</dsig:X509Data>
</dsig:KeyInfo>
</KeyDescriptor>
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8081/auth/realms/master/protocol/saml"/>
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8081/auth/realms/master/protocol/saml"/>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:2.0:nameid-format:transient
</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
</NameIDFormat>
<NameIDFormat>
urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
</NameIDFormat>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8081/auth/realms/master/protocol/saml"/>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8081/auth/realms/master/protocol/saml"/>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://localhost:8081/auth/realms/master/protocol/saml"/>
</IDPSSODescriptor>
</EntityDescriptor>
</EntitiesDescriptor>
@@ -0,0 +1,81 @@
<!--
~ 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">
<SP entityID="sp"
sslPolicy="ssl"
nameIDPolicyFormat="format"
forceAuthentication="true"
isPassive="true">
<Keys>
<Key signing="true" >
<KeyStore file="file" resource="cp" password="pw">
<PrivateKey alias="private alias" password="private pw"/>
<Certificate alias="cert alias"/>
</KeyStore>
</Key>
<Key encryption="true">
<PrivateKeyPem>
private pem
</PrivateKeyPem>
<PublicKeyPem>
public pem
</PublicKeyPem>
</Key>
</Keys>
<PrincipalNameMapping policy="policy" attribute="attribute"/>
<RoleIdentifiers>
<Attribute name="member"/>
</RoleIdentifiers>
<IDP entityID="idp"
signatureAlgorithm="RSA"
signatureCanonicalizationMethod="canon"
signaturesRequired="true"
>
<SingleSignOnService signRequest="true"
validateResponseSignature="true"
requestBinding="post"
bindingUrl="url"
/>
<SingleLogoutService
validateRequestSignature="true"
validateResponseSignature="true"
signRequest="true"
signResponse="true"
requestBinding="redirect"
responseBinding="post"
postBindingUrl="posturl"
redirectBindingUrl="redirecturl"
/>
<Keys>
<Key signing="true">
<CertificatePem>cert pem 0</CertificatePem>
</Key>
<Key signing="true">
<CertificatePem>cert pem 1</CertificatePem>
</Key>
<Key signing="true">
<CertificatePem>cert pem 2</CertificatePem>
</Key>
<Key signing="true">
<CertificatePem>cert pem 3</CertificatePem>
</Key>
</Keys>
</IDP>
</SP>
</keycloak-saml-adapter>
@@ -0,0 +1,81 @@
<!--
~ 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">
<SP entityID="sp"
sslPolicy="ssl"
nameIDPolicyFormat="format"
forceAuthentication="true"
isPassive="true">
<Keys>
<Key signing="true" >
<KeyStore file="file" resource="cp" password="pw">
<PrivateKey alias="private alias" password="private pw"/>
<Certificate alias="cert alias"/>
</KeyStore>
</Key>
<Key encryption="true">
<PrivateKeyPem>
private pem
</PrivateKeyPem>
<PublicKeyPem>
public pem
</PublicKeyPem>
</Key>
</Keys>
<PrincipalNameMapping policy="policy" attribute="attribute"/>
<RoleIdentifiers>
<Attribute name="member"/>
</RoleIdentifiers>
<IDP entityID="idp"
signatureAlgorithm="RSA"
signatureCanonicalizationMethod="canon"
signaturesRequired="true"
>
<SingleSignOnService signRequest="true"
validateResponseSignature="true"
requestBinding="post"
bindingUrl="url"
/>
<SingleLogoutService
validateRequestSignature="true"
validateResponseSignature="true"
signRequest="true"
signResponse="true"
requestBinding="redirect"
responseBinding="post"
postBindingUrl="posturl"
redirectBindingUrl="redirecturl"
/>
<Keys>
<Key signing="true">
<CertificatePem>
cert pem
</CertificatePem>
</Key>
</Keys>
<HttpClient allowAnyHostname="true"
clientKeystore="ks" clientKeystorePassword="ks-pwd"
connectionPoolSize="42"
disableTrustManager="true"
proxyUrl="pu"
truststore="ts" truststorePassword="tsp"
/>
</IDP>
</SP>
</keycloak-saml-adapter>