keycloak-memoizeit

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

11/8/2016 4:56:25 AM

Changes

adapters/saml/core/src/test/java/org/keycloak/test/adapters/saml/XmlParserTest.java 133(+0 -133)

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

Details

diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java
index 63cdab8..e6d6588 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java
@@ -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;
diff --git a/adapters/saml/core/nbproject/project.properties b/adapters/saml/core/nbproject/project.properties
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/adapters/saml/core/nbproject/project.properties
diff --git a/adapters/saml/core/pom.xml b/adapters/saml/core/pom.xml
index 16dce33..b01061b 100755
--- a/adapters/saml/core/pom.xml
+++ b/adapters/saml/core/pom.xml
@@ -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>
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/AdapterHttpClientConfig.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/AdapterHttpClientConfig.java
new file mode 100644
index 0000000..5c94fdb
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/AdapterHttpClientConfig.java
@@ -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();
+
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpAdapterUtils.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpAdapterUtils.java
new file mode 100644
index 0000000..0fa330e
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpAdapterUtils.java
@@ -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;
+    }
+
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientAdapterException.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientAdapterException.java
new file mode 100644
index 0000000..e0371ad
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientAdapterException.java
@@ -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);
+    }
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientBuilder.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientBuilder.java
new file mode 100644
index 0000000..7e26c01
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/HttpClientBuilder.java
@@ -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());
+    }
+}
\ No newline at end of file
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/SniSSLSocketFactory.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/SniSSLSocketFactory.java
new file mode 100644
index 0000000..8c064b0
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/cloned/SniSSLSocketFactory.java
@@ -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;
+    }
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/AbstractInitiateLogin.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/AbstractInitiateLogin.java
index 305ffeb..6ddf52c 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/AbstractInitiateLogin.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/AbstractInitiateLogin.java
@@ -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;
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/IDP.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/IDP.java
index 3960b46..de95d87 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/IDP.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/IDP.java
@@ -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;
+    }
+
 }
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
index 0085a6a..1a3dd04 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
@@ -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";
+
 }
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
index d6e4bce..7af71ba 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
@@ -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());
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/IDPXmlParser.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/IDPXmlParser.java
index e649d1c..be54223 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/IDPXmlParser.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/IDPXmlParser.java
@@ -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;
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
index 3eeb1f7..be6d682 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
@@ -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)
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
index ee753ad..a52cdc2 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
@@ -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;
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/descriptor/parsers/SamlDescriptorIDPKeysExtractor.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/descriptor/parsers/SamlDescriptorIDPKeysExtractor.java
new file mode 100644
index 0000000..0858675
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/descriptor/parsers/SamlDescriptorIDPKeysExtractor.java
@@ -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));
+    }
+
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
index e9247b3..429d610 100644
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
@@ -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);
+    }
 }
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/webbrowsersso/WebBrowserSsoAuthenticationHandler.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/webbrowsersso/WebBrowserSsoAuthenticationHandler.java
index 5c1454f..231c425 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/webbrowsersso/WebBrowserSsoAuthenticationHandler.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/webbrowsersso/WebBrowserSsoAuthenticationHandler.java
@@ -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");
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/rotation/SamlDescriptorPublicKeyLocator.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/rotation/SamlDescriptorPublicKeyLocator.java
new file mode 100644
index 0000000..7a45fb7
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/rotation/SamlDescriptorPublicKeyLocator.java
@@ -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();
+    }
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
index 0b82ff2..4442177 100755
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
@@ -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();
diff --git a/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_7.xsd b/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_7.xsd
new file mode 100644
index 0000000..174ea17
--- /dev/null
+++ b/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_7.xsd
@@ -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>
diff --git a/adapters/saml/core/src/test/java/org/keycloak/adapters/cloned/HttpAdapterUtilsTest.java b/adapters/saml/core/src/test/java/org/keycloak/adapters/cloned/HttpAdapterUtilsTest.java
new file mode 100644
index 0000000..2c03ef8
--- /dev/null
+++ b/adapters/saml/core/src/test/java/org/keycloak/adapters/cloned/HttpAdapterUtilsTest.java
@@ -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"));
+
+    }
+
+}
diff --git a/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java b/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java
new file mode 100755
index 0000000..10537b3
--- /dev/null
+++ b/adapters/saml/core/src/test/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParserTest.java
@@ -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));
+    }
+}
diff --git a/adapters/saml/core/src/test/resources/org/keycloak/adapters/cloned/saml-descriptor-valid.xml b/adapters/saml/core/src/test/resources/org/keycloak/adapters/cloned/saml-descriptor-valid.xml
new file mode 100644
index 0000000..8dd3e41
--- /dev/null
+++ b/adapters/saml/core/src/test/resources/org/keycloak/adapters/cloned/saml-descriptor-valid.xml
@@ -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>
\ No newline at end of file
diff --git a/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml-multiple-signing-keys.xml b/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml-multiple-signing-keys.xml
new file mode 100644
index 0000000..33b2f73
--- /dev/null
+++ b/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml-multiple-signing-keys.xml
@@ -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>
\ No newline at end of file
diff --git a/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml-wth-http-client-settings.xml b/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml-wth-http-client-settings.xml
new file mode 100644
index 0000000..36410e5
--- /dev/null
+++ b/adapters/saml/core/src/test/resources/org/keycloak/adapters/saml/config/parsers/keycloak-saml-wth-http-client-settings.xml
@@ -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>
\ No newline at end of file
diff --git a/common/src/main/java/org/keycloak/common/util/StringPropertyReplacer.java b/common/src/main/java/org/keycloak/common/util/StringPropertyReplacer.java
index 3e4839a..283eb3e 100755
--- a/common/src/main/java/org/keycloak/common/util/StringPropertyReplacer.java
+++ b/common/src/main/java/org/keycloak/common/util/StringPropertyReplacer.java
@@ -98,7 +98,7 @@ public final class StringPropertyReplacer
     public static String replaceProperties(final String string, final Properties props)
     {
         final char[] chars = string.toCharArray();
-        StringBuffer buffer = new StringBuffer();
+        StringBuilder buffer = new StringBuilder();
         boolean properties = false;
         int state = NORMAL;
         int start = 0;
diff --git a/common/src/main/java/org/keycloak/common/util/Time.java b/common/src/main/java/org/keycloak/common/util/Time.java
index ef5d174..54809d8 100644
--- a/common/src/main/java/org/keycloak/common/util/Time.java
+++ b/common/src/main/java/org/keycloak/common/util/Time.java
@@ -26,26 +26,51 @@ public class Time {
 
     private static int offset;
 
+    /**
+     * Returns current time in seconds adjusted by adding {@link #offset) seconds.
+     * @return see description
+     */
     public static int currentTime() {
         return ((int) (System.currentTimeMillis() / 1000)) + offset;
     }
 
+    /**
+     * Returns current time in milliseconds adjusted by adding {@link #offset) seconds.
+     * @return see description
+     */
     public static long currentTimeMillis() {
         return System.currentTimeMillis() + (offset * 1000);
     }
 
+    /**
+     * Returns {@link Date} object, its value set to time
+     * @param time Time in milliseconds since the epoch
+     * @return see description
+     */
     public static Date toDate(int time) {
         return new Date(((long) time ) * 1000);
     }
 
+    /**
+     * Returns time in milliseconds for a time in seconds. No adjustment is made to the parameter.
+     * @param time Time in seconds since the epoch
+     * @return Time in milliseconds
+     */
     public static long toMillis(int time) {
         return ((long) time) * 1000;
     }
 
+    /**
+     * @return Time offset in seconds that will be added to {@link #currentTime()} and {@link #currentTimeMillis()}.
+     */
     public static int getOffset() {
         return offset;
     }
 
+    /**
+     * Sets time offset in seconds that will be added to {@link #currentTime()} and {@link #currentTimeMillis()}.
+     * @param offset Offset (in seconds)
+     */
     public static void setOffset(int offset) {
         Time.offset = offset;
     }
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
index c4818b4..0ba327d 100755
--- a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
@@ -39,7 +39,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
         "proxy-url", "turn-off-change-session-id-on-login", "token-minimum-time-to-live", "min-time-between-jwks-requests",
         "policy-enforcer"
 })
-public class AdapterConfig extends BaseAdapterConfig {
+public class AdapterConfig extends BaseAdapterConfig implements AdapterHttpClientConfig {
 
     @JsonProperty("allow-any-hostname")
     protected boolean allowAnyHostname;
@@ -82,6 +82,7 @@ public class AdapterConfig extends BaseAdapterConfig {
     @JsonProperty("proxy-url")
     protected String proxyUrl;
 
+    @Override
     public boolean isAllowAnyHostname() {
         return allowAnyHostname;
     }
@@ -90,6 +91,7 @@ public class AdapterConfig extends BaseAdapterConfig {
         this.allowAnyHostname = allowAnyHostname;
     }
 
+    @Override
     public boolean isDisableTrustManager() {
         return disableTrustManager;
     }
@@ -98,6 +100,7 @@ public class AdapterConfig extends BaseAdapterConfig {
         this.disableTrustManager = disableTrustManager;
     }
 
+    @Override
     public String getTruststore() {
         return truststore;
     }
@@ -106,6 +109,7 @@ public class AdapterConfig extends BaseAdapterConfig {
         this.truststore = truststore;
     }
 
+    @Override
     public String getTruststorePassword() {
         return truststorePassword;
     }
@@ -114,6 +118,7 @@ public class AdapterConfig extends BaseAdapterConfig {
         this.truststorePassword = truststorePassword;
     }
 
+    @Override
     public String getClientKeystore() {
         return clientKeystore;
     }
@@ -122,6 +127,7 @@ public class AdapterConfig extends BaseAdapterConfig {
         this.clientKeystore = clientKeystore;
     }
 
+    @Override
     public String getClientKeystorePassword() {
         return clientKeystorePassword;
     }
@@ -138,6 +144,7 @@ public class AdapterConfig extends BaseAdapterConfig {
         this.clientKeyPassword = clientKeyPassword;
     }
 
+    @Override
     public int getConnectionPoolSize() {
         return connectionPoolSize;
     }
@@ -202,6 +209,7 @@ public class AdapterConfig extends BaseAdapterConfig {
         this.policyEnforcerConfig = policyEnforcerConfig;
     }
 
+    @Override
     public String getProxyUrl() {
         return proxyUrl;
     }
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterHttpClientConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterHttpClientConfig.java
new file mode 100644
index 0000000..fa4c87e
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterHttpClientConfig.java
@@ -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.representations.adapters.config;
+
+/**
+ * Configuration options relevant for configuring http client that can be used by adapter.
+ *
+ * NOTE: keep in sync with adapters/saml/core/src/main/java/org/keycloak/adapters/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();
+
+}
diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-api-public/main/module.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-api-public/main/module.xml
index 451497f..1438ed9 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-api-public/main/module.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-api-public/main/module.xml
@@ -30,6 +30,7 @@
         <module name="org.keycloak.keycloak-saml-core-public"/>
         <module name="org.keycloak.keycloak-saml-core"/>
         <module name="org.keycloak.keycloak-common"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-core/main/module.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-core/main/module.xml
index 49e14c4..4973aa1 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-core/main/module.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-core/main/module.xml
@@ -34,6 +34,7 @@
         <module name="org.keycloak.keycloak-saml-adapter-api-public"/>
         <module name="org.keycloak.keycloak-saml-core"/>
         <module name="org.keycloak.keycloak-common"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-as7-adapter/main/module.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-as7-adapter/main/module.xml
index 1d728f5..b85e56f 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-as7-adapter/main/module.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-as7-adapter/main/module.xml
@@ -41,6 +41,7 @@
         <module name="org.keycloak.keycloak-saml-adapter-api-public"/>
         <module name="org.keycloak.keycloak-saml-adapter-core"/>
         <module name="org.keycloak.keycloak-common"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-as7-subsystem/main/module.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-as7-subsystem/main/module.xml
index d720b02..9d1a63e 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-as7-subsystem/main/module.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-as7-subsystem/main/module.xml
@@ -39,5 +39,6 @@
         <module name="org.jboss.logging"/>
         <module name="org.jboss.vfs"/>
         <module name="org.jboss.metadata"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 </module>
diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core/main/module.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core/main/module.xml
index aac9500..7851fd5 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core/main/module.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core/main/module.xml
@@ -36,6 +36,7 @@
             </imports>
         </module>
         <module name="javax.api"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core-public/main/module.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core-public/main/module.xml
index 550dac8..35977c7 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core-public/main/module.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core-public/main/module.xml
@@ -32,6 +32,7 @@
             </imports>
         </module>
         <module name="javax.api"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml
index f04205b..40186d7 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml
@@ -29,6 +29,7 @@
         <module name="org.picketbox"/>
         <module name="org.keycloak.keycloak-adapter-spi"/>
         <module name="org.keycloak.keycloak-common"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-api-public/main/module.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-api-public/main/module.xml
index 451497f..1438ed9 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-api-public/main/module.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-api-public/main/module.xml
@@ -30,6 +30,7 @@
         <module name="org.keycloak.keycloak-saml-core-public"/>
         <module name="org.keycloak.keycloak-saml-core"/>
         <module name="org.keycloak.keycloak-common"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-core/main/module.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-core/main/module.xml
index 34e6895..e19e0f0 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-core/main/module.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-core/main/module.xml
@@ -34,6 +34,7 @@
         <module name="org.keycloak.keycloak-saml-core-public"/>
         <module name="org.keycloak.keycloak-saml-core"/>
         <module name="org.keycloak.keycloak-common"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core/main/module.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core/main/module.xml
index aac9500..7851fd5 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core/main/module.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core/main/module.xml
@@ -36,6 +36,7 @@
             </imports>
         </module>
         <module name="javax.api"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core-public/main/module.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core-public/main/module.xml
index e5e572c..5637635 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core-public/main/module.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core-public/main/module.xml
@@ -35,6 +35,7 @@
             </imports>
         </module>
         <module name="javax.api"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-undertow-adapter/main/module.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-undertow-adapter/main/module.xml
index 397901c..d4cefc5 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-undertow-adapter/main/module.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-undertow-adapter/main/module.xml
@@ -40,6 +40,7 @@
         <module name="org.keycloak.keycloak-saml-adapter-api-public"/>
         <module name="org.keycloak.keycloak-saml-adapter-core"/>
         <module name="org.keycloak.keycloak-common"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-wildfly-adapter/main/module.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-wildfly-adapter/main/module.xml
index 3027115..ee00fcc 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-wildfly-adapter/main/module.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-wildfly-adapter/main/module.xml
@@ -41,6 +41,7 @@
         <module name="org.keycloak.keycloak-saml-adapter-api-public"/>
         <module name="org.keycloak.keycloak-saml-adapter-core"/>
         <module name="org.keycloak.keycloak-common"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 
 </module>
diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-wildfly-subsystem/main/module.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-wildfly-subsystem/main/module.xml
index cda8970..857a8e3 100755
--- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-wildfly-subsystem/main/module.xml
+++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-wildfly-subsystem/main/module.xml
@@ -39,5 +39,6 @@
         <module name="org.jboss.vfs"/>
         <module name="org.jboss.as.web-common"/>
         <module name="org.jboss.metadata"/>
+        <module name="org.apache.httpcomponents"/>
     </dependencies>
 </module>
diff --git a/saml-core/nbproject/project.properties b/saml-core/nbproject/project.properties
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/saml-core/nbproject/project.properties

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

diff --git a/saml-core/pom.xml b/saml-core/pom.xml
index 8483240..8c08b69 100755
--- a/saml-core/pom.xml
+++ b/saml-core/pom.xml
@@ -53,6 +53,18 @@
             <groupId>org.apache.santuario</groupId>
             <artifactId>xmlsec</artifactId>
         </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-core</artifactId>
+            <version>1.3</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     <build>
         <resources>
diff --git a/saml-core/src/main/java/org/keycloak/rotation/CompositeKeyLocator.java b/saml-core/src/main/java/org/keycloak/rotation/CompositeKeyLocator.java
new file mode 100644
index 0000000..4b3cb57
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/rotation/CompositeKeyLocator.java
@@ -0,0 +1,159 @@
+/*
+ * 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.rotation;
+
+import java.security.Key;
+import java.security.KeyManagementException;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * {@link KeyLocator} that represents a list of multiple {@link KeyLocator}s. Key is searched
+ * from the first to the last {@link KeyLocator} in the order given by the list. If there are
+ * multiple {@link KeyLocator}s providing key with the same key ID, the first matching key is
+ * returned.
+ *
+ * @author hmlnarik
+ */
+public class CompositeKeyLocator implements KeyLocator, Iterable<Key> {
+
+    private final List<KeyLocator> keyLocators = new LinkedList<>();
+
+    @Override
+    public Key getKey(String kid) throws KeyManagementException {
+        for (KeyLocator keyLocator : keyLocators) {
+            Key k = keyLocator.getKey(kid);
+            if (k != null) {
+                return k;
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public void refreshKeyCache() {
+        for (KeyLocator keyLocator : keyLocators) {
+            keyLocator.refreshKeyCache();
+        }
+    }
+
+    /**
+     * Registers a given {@link KeyLocator} as the first {@link KeyLocator}.
+     */
+    public void addFirst(KeyLocator keyLocator) {
+        this.keyLocators.add(0, keyLocator);
+    }
+
+    /**
+     * Registers a given {@link KeyLocator} as the last {@link KeyLocator}.
+     */
+    public void add(KeyLocator keyLocator) {
+        this.keyLocators.add(keyLocator);
+    }
+
+    /**
+     * Clears the list of registered {@link KeyLocator}s
+     */
+    public void clear() {
+        this.keyLocators.clear();
+    }
+
+    @Override
+    public String toString() {
+        if (this.keyLocators.size() == 1) {
+            return this.keyLocators.get(0).toString();
+        }
+
+        StringBuilder sb = new StringBuilder("Key locator chain: [");
+        for (Iterator<KeyLocator> it = keyLocators.iterator(); it.hasNext();) {
+            KeyLocator keyLocator = it.next();
+            sb.append(keyLocator.toString());
+            if (it.hasNext()) {
+                sb.append(", ");
+            }
+        }
+        return sb.append("]").toString();
+    }
+
+    @Override
+    public Iterator<Key> iterator() {
+        final Iterator<Iterable<Key>> iterablesIterator = getKeyLocatorIterators().iterator();
+
+        return new JointKeyIterator(iterablesIterator).iterator();
+    }
+
+    @SuppressWarnings("unchecked")
+    private Iterable<Iterable<Key>> getKeyLocatorIterators() {
+        List<Iterable<Key>> res = new LinkedList<>();
+        for (KeyLocator kl : this.keyLocators) {
+            if (kl instanceof Iterable) {
+                res.add(((Iterable<Key>) kl));
+            }
+        }
+        return Collections.unmodifiableCollection(res);
+    }
+
+    private class JointKeyIterator implements Iterable<Key> {
+
+        // based on http://stackoverflow.com/a/34126154/6930869
+        private final Iterator<Iterable<Key>> iterablesIterator;
+
+        public JointKeyIterator(Iterator<Iterable<Key>> iterablesIterator) {
+            this.iterablesIterator = iterablesIterator;
+        }
+
+        @Override
+        public Iterator<Key> iterator() {
+            if (! iterablesIterator.hasNext()) {
+                return Collections.<Key>emptyIterator();
+            }
+
+            return new Iterator<Key>() {
+                private Iterator<Key> currentIterator = nextIterator();
+
+                @Override
+                public boolean hasNext() {
+                    return currentIterator.hasNext();
+                }
+
+                @Override
+                public Key next() {
+                    final Key next = currentIterator.next();
+                    findNext();
+                    return next;
+                }
+
+                private Iterator<Key> nextIterator() {
+                    return iterablesIterator.next().iterator();
+                }
+
+                private Iterator<Key> findNext() {
+                    while (! currentIterator.hasNext()) {
+                        if (! iterablesIterator.hasNext()) {
+                            break;
+                        }
+                        currentIterator = nextIterator();
+                    }
+                    return this;
+                }
+            }.findNext();
+        }
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/rotation/HardcodedKeyLocator.java b/saml-core/src/main/java/org/keycloak/rotation/HardcodedKeyLocator.java
new file mode 100644
index 0000000..ae2615a
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/rotation/HardcodedKeyLocator.java
@@ -0,0 +1,69 @@
+/*
+ * 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.rotation;
+
+import java.security.Key;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ * Key locator that always returns a specified key.
+ *
+ * @author <a href="mailto:hmlnarik@redhat.com">Hynek Mlnařík</a>
+ */
+public class HardcodedKeyLocator implements KeyLocator, Iterable<Key> {
+
+    private final Collection<? extends Key> keys;
+
+    public HardcodedKeyLocator(Key key) {
+        this.keys = Collections.singleton(key);
+    }
+
+    public HardcodedKeyLocator(Collection<? extends Key> keys) {
+        if (keys == null) {
+            throw new NullPointerException("keys");
+        }
+        this.keys = new LinkedList<>(keys);
+    }
+
+    @Override
+    public Key getKey(String kid) {
+        if (this.keys.size() == 1) {
+            return this.keys.iterator().next();
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void refreshKeyCache() {
+        // do nothing
+    }
+
+    @Override
+    public String toString() {
+        return "hardcoded keys, count: " + this.keys.size();
+    }
+
+    @Override
+    public Iterator<Key> iterator() {
+        return Collections.unmodifiableCollection(keys).iterator();
+    }
+}
diff --git a/saml-core/src/main/java/org/keycloak/rotation/KeyLocator.java b/saml-core/src/main/java/org/keycloak/rotation/KeyLocator.java
new file mode 100644
index 0000000..7112eca
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/rotation/KeyLocator.java
@@ -0,0 +1,50 @@
+/*
+ * 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.rotation;
+
+import java.security.Key;
+import java.security.KeyManagementException;
+
+/**
+ * This interface defines a method for obtaining a security key by ID.
+ * <p>
+ * If the {@code KeyLocator} implementor wants to make all its keys available for iteration,
+ * it should implement {@link Iterable}&lt;{@code T extends }{@link Key}&gt; interface.
+ * The base {@code KeyLocator} does not extend this interface to enable {@code KeyLocators}
+ * that do not support listing their keys.
+ *
+ * @author <a href="mailto:hmlnarik@redhat.com">Hynek Mlnařík</a>
+ */
+public interface KeyLocator {
+
+    /**
+     * Returns a key with a particular ID.
+     * @param kid Key ID
+     * @param configuration Configuration
+     * @return key, which should be used for verify signature on given "input"
+     * @throws KeyManagementException
+     */
+    Key getKey(String kid) throws KeyManagementException;
+
+    /**
+     * If this key locator caches keys in any way, forces this cache cleanup
+     * and refreshing the keys.
+     */
+    void refreshKeyCache();
+
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java b/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java
index 6d84c13..f820a5e 100755
--- a/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java
@@ -38,11 +38,14 @@ import javax.crypto.spec.SecretKeySpec;
 import javax.xml.crypto.dsig.CanonicalizationMethod;
 import javax.xml.namespace.QName;
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
 import java.net.URI;
+import java.security.InvalidKeyException;
 import java.security.KeyPair;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.Signature;
+import java.security.SignatureException;
 import java.security.cert.X509Certificate;
 
 import static org.keycloak.common.util.HtmlUtils.escapeAttribute;
@@ -55,6 +58,7 @@ import static org.keycloak.saml.common.util.StringUtil.isNotNull;
 public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
     protected static final Logger logger = Logger.getLogger(BaseSAML2BindingBuilder.class);
 
+    protected String signingKeyId;
     protected KeyPair signingKeyPair;
     protected X509Certificate signingCertificate;
     protected boolean sign;
@@ -82,23 +86,27 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
         return (T)this;
     }
 
-    public T signWith(KeyPair keyPair) {
+    public T signWith(String signingKeyId, KeyPair keyPair) {
+        this.signingKeyId = signingKeyId;
         this.signingKeyPair = keyPair;
         return (T)this;
     }
 
-    public T signWith(PrivateKey privateKey, PublicKey publicKey) {
+    public T signWith(String signingKeyId, PrivateKey privateKey, PublicKey publicKey) {
+        this.signingKeyId = signingKeyId;
         this.signingKeyPair = new KeyPair(publicKey, privateKey);
         return (T)this;
     }
 
-    public T signWith(KeyPair keyPair, X509Certificate cert) {
+    public T signWith(String signingKeyId, KeyPair keyPair, X509Certificate cert) {
+        this.signingKeyId = signingKeyId;
         this.signingKeyPair = keyPair;
         this.signingCertificate = cert;
         return (T)this;
     }
 
-    public T signWith(PrivateKey privateKey, PublicKey publicKey, X509Certificate cert) {
+    public T signWith(String signingKeyId, PrivateKey privateKey, PublicKey publicKey, X509Certificate cert) {
+        this.signingKeyId = signingKeyId;
         this.signingKeyPair = new KeyPair(publicKey, privateKey);
         this.signingCertificate = cert;
         return (T)this;
@@ -263,7 +271,7 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
             samlSignature.setX509Certificate(signingCertificate);
         }
 
-        samlSignature.signSAMLDocument(samlDocument, signingKeyPair, canonicalizationMethodType);
+        samlSignature.signSAMLDocument(samlDocument, signingKeyId, signingKeyPair, canonicalizationMethodType);
     }
 
     public void signAssertion(Document samlDocument) throws ProcessingException {
@@ -333,7 +341,7 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
 
     public String base64Encoded(Document document) throws ConfigurationException, ProcessingException, IOException  {
         String documentAsString = DocumentUtil.getDocumentAsString(document);
-        logger.debugv("saml docment: {0}", documentAsString);
+        logger.debugv("saml document: {0}", documentAsString);
         byte[] responseBytes = documentAsString.getBytes("UTF-8");
 
         return RedirectBindingUtil.deflateBase64URLEncode(responseBytes);
@@ -358,7 +366,7 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
                 signature.initSign(signingKeyPair.getPrivate());
                 signature.update(rawQuery.getBytes("UTF-8"));
                 sig = signature.sign();
-            } catch (Exception e) {
+            } catch (InvalidKeyException | UnsupportedEncodingException | SignatureException e) {
                 throw new ProcessingException(e);
             }
             String encodedSig = RedirectBindingUtil.base64URLEncode(sig);
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/DefaultPicketLinkLogger.java b/saml-core/src/main/java/org/keycloak/saml/common/DefaultPicketLinkLogger.java
index 76a72ae..2f58911 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/DefaultPicketLinkLogger.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/DefaultPicketLinkLogger.java
@@ -450,6 +450,11 @@ public class DefaultPicketLinkLogger implements PicketLinkLogger {
         return new RuntimeException(ErrorCodes.EXPECTED_TAG + tag + ">.  Found <" + foundElementTag + ">");
     }
 
+    @Override
+    public RuntimeException parserExpectedNamespace(String ns, String foundElementNs) {
+        return new RuntimeException(ErrorCodes.EXPECTED_NAMESPACE + ns + ">.  Found <" + foundElementNs + ">");
+    }
+
     /*
      *(non-Javadoc)
      *
@@ -2378,4 +2383,10 @@ public class DefaultPicketLinkLogger implements PicketLinkLogger {
         return new ProcessingException("Wrong audience [" + serviceURL + "].");
     }
 
+    @Override
+    public ProcessingException samlExtensionUnknownChild(Class<?> clazz) {
+        return new ProcessingException("Unknown child type specified for extension: " 
+          + (clazz == null ? "<null>" : clazz.getSimpleName())
+          + ".");
+    }
 }
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/ErrorCodes.java b/saml-core/src/main/java/org/keycloak/saml/common/ErrorCodes.java
index 09f4301..37a7755 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/ErrorCodes.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/ErrorCodes.java
@@ -48,6 +48,8 @@ public interface ErrorCodes {
 
     String EXPECTED_TAG = "PL00066: Parser : Expected start tag:";
 
+    String EXPECTED_NAMESPACE = "PL00107: Parser : Expected start element namespace:";
+
     String EXPECTED_TEXT_VALUE = "PL00071: Parser: Expected text value:";
 
     String EXPECTED_END_TAG = "PL00066: Parser : Expected end tag:";
diff --git a/saml-core/src/main/java/org/keycloak/saml/common/PicketLinkLogger.java b/saml-core/src/main/java/org/keycloak/saml/common/PicketLinkLogger.java
index 7ac6a09..91f2f54 100755
--- a/saml-core/src/main/java/org/keycloak/saml/common/PicketLinkLogger.java
+++ b/saml-core/src/main/java/org/keycloak/saml/common/PicketLinkLogger.java
@@ -297,6 +297,14 @@ public interface PicketLinkLogger {
     RuntimeException parserExpectedTag(String tag, String foundElementTag);
 
     /**
+     * @param ns
+     * @param foundElementNs
+     *
+     * @return
+     */
+    RuntimeException parserExpectedNamespace(String ns, String foundElementNs);
+
+    /**
      * @param elementName
      *
      * @return
@@ -1219,4 +1227,6 @@ public interface PicketLinkLogger {
     RuntimeException parserFeatureNotSupported(String feature);
 
     ProcessingException samlAssertionWrongAudience(String serviceURL);
+
+    ProcessingException samlExtensionUnknownChild(Class<?> clazz);
 }
\ No newline at end of file
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java
index 5ac8ce1..49c8df8 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/api/saml/v2/sig/SAML2Signature.java
@@ -35,8 +35,8 @@ import javax.xml.crypto.dsig.XMLSignatureException;
 import javax.xml.parsers.ParserConfigurationException;
 import java.security.GeneralSecurityException;
 import java.security.KeyPair;
-import java.security.PublicKey;
 import java.security.cert.X509Certificate;
+import org.keycloak.rotation.KeyLocator;
 
 /**
  * Class that deals with SAML2 Signature
@@ -121,7 +121,7 @@ public class SAML2Signature {
      * @throws MarshalException
      * @throws GeneralSecurityException
      */
-    public Document sign(Document doc, String referenceID, KeyPair keyPair, String canonicalizationMethodType) throws ParserConfigurationException,
+    public Document sign(Document doc, String referenceID, String keyId, KeyPair keyPair, String canonicalizationMethodType) throws ParserConfigurationException,
             GeneralSecurityException, MarshalException, XMLSignatureException {
         String referenceURI = "#" + referenceID;
 
@@ -130,6 +130,7 @@ public class SAML2Signature {
         if (sibling != null) {
             SignatureUtilTransferObject dto = new SignatureUtilTransferObject();
             dto.setDocumentToBeSigned(doc);
+            dto.setKeyId(keyId);
             dto.setKeyPair(keyPair);
             dto.setDigestMethod(digestMethod);
             dto.setSignatureMethod(signatureMethod);
@@ -142,7 +143,7 @@ public class SAML2Signature {
 
             return XMLSignatureUtil.sign(dto, canonicalizationMethodType);
         }
-        return XMLSignatureUtil.sign(doc, keyPair, digestMethod, signatureMethod, referenceURI, canonicalizationMethodType);
+        return XMLSignatureUtil.sign(doc, keyId, keyPair, digestMethod, signatureMethod, referenceURI, canonicalizationMethodType);
     }
 
     /**
@@ -153,12 +154,12 @@ public class SAML2Signature {
      *
      * @throws org.keycloak.saml.common.exceptions.ProcessingException
      */
-    public void signSAMLDocument(Document samlDocument, KeyPair keypair, String canonicalizationMethodType) throws ProcessingException {
+    public void signSAMLDocument(Document samlDocument, String keyId, KeyPair keypair, String canonicalizationMethodType) throws ProcessingException {
         // Get the ID from the root
         String id = samlDocument.getDocumentElement().getAttribute(ID_ATTRIBUTE_NAME);
         try {
-            sign(samlDocument, id, keypair, canonicalizationMethodType);
-        } catch (Exception e) {
+            sign(samlDocument, id, keyId, keypair, canonicalizationMethodType);
+        } catch (ParserConfigurationException | GeneralSecurityException | MarshalException | XMLSignatureException e) {
             throw new ProcessingException(logger.signatureError(e));
         }
     }
@@ -167,20 +168,18 @@ public class SAML2Signature {
      * Validate the SAML2 Document
      *
      * @param signedDocument
-     * @param publicKey
+     * @param keyLocator
      *
      * @return
      *
      * @throws ProcessingException
      */
-    public boolean validate(Document signedDocument, PublicKey publicKey) throws ProcessingException {
+    public boolean validate(Document signedDocument, KeyLocator keyLocator) throws ProcessingException {
         try {
             configureIdAttribute(signedDocument);
-            return XMLSignatureUtil.validate(signedDocument, publicKey);
-        } catch (MarshalException me) {
+            return XMLSignatureUtil.validate(signedDocument, keyLocator);
+        } catch (MarshalException | XMLSignatureException me) {
             throw new ProcessingException(logger.signatureError(me));
-        } catch (XMLSignatureException xse) {
-            throw new ProcessingException(logger.signatureError(xse));
         }
     }
 
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/api/util/KeyInfoTools.java b/saml-core/src/main/java/org/keycloak/saml/processing/api/util/KeyInfoTools.java
new file mode 100644
index 0000000..be9bf51
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/api/util/KeyInfoTools.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.saml.processing.api.util;
+
+import java.security.cert.X509Certificate;
+import javax.xml.crypto.dsig.keyinfo.KeyInfo;
+import javax.xml.crypto.dsig.keyinfo.KeyName;
+import javax.xml.crypto.dsig.keyinfo.X509Data;
+
+/**
+ * Tools for {@link KeyInfo} object manipulation.
+ * @author hmlnarik
+ */
+public class KeyInfoTools {
+
+    /**
+     * Returns the first object of the given class from the given Iterable.
+     * @param <T>
+     * @param objects
+     * @param clazz
+     * @return The object or {@code null} if not found.
+     */
+    public static <T> T getContent(Iterable<Object> objects, Class<T> clazz) {
+        for (Object o : objects) {
+            if (clazz.isInstance(o)) {
+                return (T) o;
+            }
+        }
+        return null;
+    }
+
+
+    public static KeyName getKeyName(KeyInfo keyInfo) {
+        return getContent(keyInfo.getContent(), KeyName.class);
+    }
+
+    public static X509Data getX509Data(KeyInfo keyInfo) {
+        return getContent(keyInfo.getContent(), X509Data.class);
+    }
+
+    public static X509Certificate getX509Certificate(KeyInfo keyInfo) {
+        X509Data d = getX509Data(keyInfo);
+        return d == null ? null : getContent(d.getContent(), X509Certificate.class);
+    }
+
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResolveParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResolveParser.java
index ca0316b..06d6042 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResolveParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResolveParser.java
@@ -58,6 +58,8 @@ public class SAMLArtifactResolveParser extends SAMLRequestAbstractParser impleme
                 continue;
             } else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
                 continue;
+            } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+                continue;
             } else
                 throw new RuntimeException(ErrorCodes.UNKNOWN_START_ELEMENT + elementName + "::location="
                         + startElement.getLocation());
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResponseParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResponseParser.java
index 5f98403..9d3686b 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResponseParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLArtifactResponseParser.java
@@ -68,6 +68,9 @@ public class SAMLArtifactResponseParser extends SAMLStatusResponseTypeParser imp
             } else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
                 Element sig = StaxParserUtil.getDOMElement(xmlEventReader);
                 response.setSignature(sig);
+            } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+                SAMLExtensionsParser extensionsParser = new SAMLExtensionsParser();
+                response.setExtensions(extensionsParser.parse(xmlEventReader));
             } else if (JBossSAMLConstants.AUTHN_REQUEST.get().equals(elementName)) {
                 SAMLAuthNRequestParser authnParser = new SAMLAuthNRequestParser();
                 AuthnRequestType authn = (AuthnRequestType) authnParser.parse(xmlEventReader);
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeQueryParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeQueryParser.java
index 139e4e0..6102e9e 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeQueryParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAttributeQueryParser.java
@@ -60,6 +60,8 @@ public class SAMLAttributeQueryParser extends SAMLRequestAbstractParser implemen
                 continue;
             } else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
                 continue;
+            } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+                continue;
             } else
                 throw new RuntimeException(ErrorCodes.UNKNOWN_START_ELEMENT + elementName + "::location="
                         + startElement.getLocation());
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAuthNRequestParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAuthNRequestParser.java
index 5a15b2c..f1d3349 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAuthNRequestParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLAuthNRequestParser.java
@@ -76,6 +76,8 @@ public class SAMLAuthNRequestParser extends SAMLRequestAbstractParser implements
                 continue;
             } else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
                 continue;
+            } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+                continue;
             } else
                 throw new RuntimeException(ErrorCodes.UNKNOWN_START_ELEMENT + elementName + "::location="
                         + startElement.getLocation());
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLExtensionsParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLExtensionsParser.java
new file mode 100644
index 0000000..5f7ebbb
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLExtensionsParser.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.saml.processing.core.parsers.saml;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
+import org.keycloak.saml.common.PicketLinkLogger;
+import org.keycloak.saml.common.PicketLinkLoggerFactory;
+import org.keycloak.saml.common.constants.JBossSAMLConstants;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.parsers.ParserNamespaceSupport;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+/**
+ * Parses &lt;samlp:Extensions&gt; SAML2 element into series of DOM nodes.
+ *
+ * @author hmlnarik
+ */
+public class SAMLExtensionsParser implements ParserNamespaceSupport {
+
+    private static final String EXTENSIONS = JBossSAMLConstants.EXTENSIONS.get();
+
+    private static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
+
+    @Override
+    public ExtensionsType parse(XMLEventReader xmlEventReader) throws ParsingException {
+        // Get the startelement
+        StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
+        StaxParserUtil.validate(startElement, EXTENSIONS);
+
+        ExtensionsType extensions = new ExtensionsType();
+
+        while (xmlEventReader.hasNext()) {
+            XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
+            if (xmlEvent instanceof EndElement) {
+                EndElement endElement = (EndElement) xmlEvent;
+                if (StaxParserUtil.matches(endElement, EXTENSIONS)) {
+                    endElement = StaxParserUtil.getNextEndElement(xmlEventReader);
+                    break;
+                } else
+                    throw logger.parserUnknownEndElement(StaxParserUtil.getEndElementName(endElement));
+            }
+
+            startElement = StaxParserUtil.peekNextStartElement(xmlEventReader);
+            if (startElement == null)
+                break;
+
+            extensions.addExtension(StaxParserUtil.getDOMElement(xmlEventReader));
+        }
+
+        return extensions;
+    }
+
+    @Override
+    public boolean supports(QName qname) {
+        String nsURI = qname.getNamespaceURI();
+        String localPart = qname.getLocalPart();
+
+        return nsURI.equals(JBossSAMLURIConstants.PROTOCOL_NSURI.get())
+                && localPart.equals(JBossSAMLConstants.EXTENSIONS.get());
+    }
+
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLResponseParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLResponseParser.java
index a2691c2..92eaf90 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLResponseParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLResponseParser.java
@@ -71,6 +71,9 @@ public class SAMLResponseParser extends SAMLStatusResponseTypeParser implements 
             } else if (JBossSAMLConstants.ASSERTION.get().equals(elementName)) {
                 SAMLAssertionParser assertionParser = new SAMLAssertionParser();
                 response.addAssertion(new RTChoiceType((AssertionType) assertionParser.parse(xmlEventReader)));
+            } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+                SAMLExtensionsParser extensionsParser = new SAMLExtensionsParser();
+                response.setExtensions(extensionsParser.parse(xmlEventReader));
             } else if (JBossSAMLConstants.STATUS.get().equals(elementName)) {
                 response.setStatus(parseStatus(xmlEventReader));
             } else if (JBossSAMLConstants.ENCRYPTED_ASSERTION.get().equals(elementName)) {
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloRequestParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloRequestParser.java
index f604cf5..22ed383 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloRequestParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloRequestParser.java
@@ -74,6 +74,8 @@ public class SAMLSloRequestParser extends SAMLRequestAbstractParser implements P
                 continue;
             } else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
                 continue;
+            } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+                continue;
             } else
                 throw logger.parserUnknownTag(elementName, startElement.getLocation());
         }
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloResponseParser.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloResponseParser.java
index 167a3c5..c0f473b 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloResponseParser.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/parsers/saml/SAMLSloResponseParser.java
@@ -60,6 +60,9 @@ public class SAMLSloResponseParser extends SAMLStatusResponseTypeParser implemen
             } else if (JBossSAMLConstants.SIGNATURE.get().equals(elementName)) {
                 startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
                 StaxParserUtil.bypassElementBlock(xmlEventReader, JBossSAMLConstants.SIGNATURE.get());
+            } else if (JBossSAMLConstants.EXTENSIONS.get().equals(elementName)) {
+                SAMLExtensionsParser extensionsParser = new SAMLExtensionsParser();
+                response.setExtensions(extensionsParser.parse(xmlEventReader));
             } else if (JBossSAMLConstants.STATUS.get().equals(elementName)) {
                 response.setStatus(parseStatus(xmlEventReader));
             }
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
index 67fb78f..ed941a0 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/util/AssertionUtil.java
@@ -62,6 +62,7 @@ import java.security.PublicKey;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
+import org.keycloak.rotation.HardcodedKeyLocator;
 
 /**
  * Utility to deal with assertions
@@ -276,7 +277,7 @@ public class AssertionUtil {
             Node n = doc.importNode(assertionElement, true);
             doc.appendChild(n);
 
-            return new SAML2Signature().validate(doc, publicKey);
+            return new SAML2Signature().validate(doc, new HardcodedKeyLocator(publicKey));
         } catch (Exception e) {
             logger.signatureAssertionValidationError(e);
         }
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java
index 4c041d1..068c91a 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/BaseWriter.java
@@ -43,8 +43,12 @@ import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
+import org.keycloak.saml.SamlProtocolExtensionsAwareBuilder;
 
 import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.ASSERTION_NSURI;
+import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.PROTOCOL_NSURI;
+import org.w3c.dom.Node;
 
 /**
  * Base Class for the Stax writers for SAML
@@ -244,6 +248,28 @@ public class BaseWriter {
         StaxUtil.flush(writer);
     }
 
+    public void write(ExtensionsType extensions) throws ProcessingException {
+        if (extensions.getAny().isEmpty()) {
+            return;
+        }
+
+        StaxUtil.writeStartElement(writer, PROTOCOL_PREFIX, JBossSAMLConstants.EXTENSIONS.get(), PROTOCOL_NSURI.get());
+
+        for (Object o : extensions.getAny()) {
+            if (o instanceof Node) {
+                StaxUtil.writeDOMNode(writer, (Node) o);
+            } else if (o instanceof SamlProtocolExtensionsAwareBuilder.NodeGenerator) {
+                SamlProtocolExtensionsAwareBuilder.NodeGenerator ng = (SamlProtocolExtensionsAwareBuilder.NodeGenerator) o;
+                ng.write(writer);
+            } else {
+                throw logger.samlExtensionUnknownChild(o == null ? null : o.getClass());
+            }
+        }
+
+        StaxUtil.writeEndElement(writer);
+        StaxUtil.flush(writer);
+    }
+
     private void write(SubjectConfirmationType subjectConfirmationType) throws ProcessingException {
         StaxUtil.writeStartElement(writer, ASSERTION_PREFIX, JBossSAMLConstants.SUBJECT_CONFIRMATION.get(),
                 ASSERTION_NSURI.get());
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLRequestWriter.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLRequestWriter.java
index 9f99780..8c115f5 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLRequestWriter.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLRequestWriter.java
@@ -36,6 +36,7 @@ import javax.xml.namespace.QName;
 import javax.xml.stream.XMLStreamWriter;
 import java.net.URI;
 import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
 
 import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.ASSERTION_NSURI;
 import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.PROTOCOL_NSURI;
@@ -122,6 +123,11 @@ public class SAMLRequestWriter extends BaseWriter {
             StaxUtil.writeDOMElement(writer, sig);
         }
 
+        ExtensionsType extensions = request.getExtensions();
+        if (extensions != null && ! extensions.getAny().isEmpty()) {
+            write(extensions);
+        }
+
         NameIDPolicyType nameIDPolicy = request.getNameIDPolicy();
         if (nameIDPolicy != null) {
             write(nameIDPolicy);
@@ -171,6 +177,11 @@ public class SAMLRequestWriter extends BaseWriter {
             StaxUtil.writeDOMElement(writer, signature);
         }
 
+        ExtensionsType extensions = logOutRequest.getExtensions();
+        if (extensions != null && ! extensions.getAny().isEmpty()) {
+            write(extensions);
+        }
+
         NameIDType nameID = logOutRequest.getNameID();
         if (nameID != null) {
             write(nameID, new QName(ASSERTION_NSURI.get(), JBossSAMLConstants.NAMEID.get(), ASSERTION_PREFIX));
@@ -278,6 +289,11 @@ public class SAMLRequestWriter extends BaseWriter {
         if (sig != null) {
             StaxUtil.writeDOMElement(writer, sig);
         }
+        ExtensionsType extensions = request.getExtensions();
+        if (extensions != null && ! extensions.getAny().isEmpty()) {
+            write(extensions);
+        }
+
         String artifact = request.getArtifact();
         if (StringUtil.isNotNull(artifact)) {
             StaxUtil.writeStartElement(writer, PROTOCOL_PREFIX, JBossSAMLConstants.ARTIFACT.get(), PROTOCOL_NSURI.get());
@@ -315,6 +331,10 @@ public class SAMLRequestWriter extends BaseWriter {
         if (sig != null) {
             StaxUtil.writeDOMElement(writer, sig);
         }
+        ExtensionsType extensions = request.getExtensions();
+        if (extensions != null && ! extensions.getAny().isEmpty()) {
+            write(extensions);
+        }
         SubjectType subject = request.getSubject();
         if (subject != null) {
             write(subject);
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLResponseWriter.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLResponseWriter.java
index 07fae2a..9327a73 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLResponseWriter.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/saml/v2/writers/SAMLResponseWriter.java
@@ -37,6 +37,7 @@ import javax.xml.namespace.QName;
 import javax.xml.stream.XMLStreamWriter;
 import java.net.URI;
 import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
 
 /**
  * Write a SAML Response to stream
@@ -78,6 +79,10 @@ public class SAMLResponseWriter extends BaseWriter {
         if (sig != null) {
             StaxUtil.writeDOMElement(writer, sig);
         }
+        ExtensionsType extensions = response.getExtensions();
+        if (extensions != null && extensions.getAny() != null && ! extensions.getAny().isEmpty()) {
+            write(extensions);
+        }
 
         StatusType status = response.getStatus();
         write(status);
@@ -119,6 +124,10 @@ public class SAMLResponseWriter extends BaseWriter {
         if (sig != null) {
             StaxUtil.writeDOMElement(writer, sig);
         }
+        ExtensionsType extensions = response.getExtensions();
+        if (extensions != null && extensions.getAny() != null && ! extensions.getAny().isEmpty()) {
+            write(extensions);
+        }
 
         StatusType status = response.getStatus();
         if (status != null) {
@@ -163,6 +172,15 @@ public class SAMLResponseWriter extends BaseWriter {
         NameIDType issuer = response.getIssuer();
         write(issuer, new QName(JBossSAMLURIConstants.ASSERTION_NSURI.get(), JBossSAMLConstants.ISSUER.get(), ASSERTION_PREFIX));
 
+        Element sig = response.getSignature();
+        if (sig != null) {
+            StaxUtil.writeDOMElement(writer, sig);
+        }
+        ExtensionsType extensions = response.getExtensions();
+        if (extensions != null && extensions.getAny() != null && ! extensions.getAny().isEmpty()) {
+            write(extensions);
+        }
+
         StatusType status = response.getStatus();
         write(status);
 
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/KeycloakKeySamlExtensionGenerator.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/KeycloakKeySamlExtensionGenerator.java
new file mode 100644
index 0000000..1bb90ea
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/KeycloakKeySamlExtensionGenerator.java
@@ -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.saml.processing.core.util;
+
+import java.util.Objects;
+import javax.xml.stream.XMLStreamWriter;
+import org.keycloak.saml.SamlProtocolExtensionsAwareBuilder;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+import org.keycloak.saml.common.util.StaxUtil;
+import org.w3c.dom.Element;
+
+/**
+ *
+ * @author hmlnarik
+ */
+public class KeycloakKeySamlExtensionGenerator implements SamlProtocolExtensionsAwareBuilder.NodeGenerator {
+
+    public static final String NS_URI = "urn:keycloak:ext:key:1.0";
+
+    public static final String NS_PREFIX = "kckey";
+
+    public static final String KC_KEY_INFO_ELEMENT_NAME = "KeyInfo";
+
+    public static final String KEY_ID_ATTRIBUTE_NAME = "MessageSigningKeyId";
+
+    private final String keyId;
+
+    public KeycloakKeySamlExtensionGenerator(String keyId) {
+        this.keyId = keyId;
+    }
+
+    @Override
+    public void write(XMLStreamWriter writer) throws ProcessingException {
+        StaxUtil.writeStartElement(writer, NS_PREFIX, KC_KEY_INFO_ELEMENT_NAME, NS_URI);
+        StaxUtil.writeNameSpace(writer, NS_PREFIX, NS_URI);
+        if (this.keyId != null) {
+            StaxUtil.writeAttribute(writer, KEY_ID_ATTRIBUTE_NAME, this.keyId);
+        }
+        StaxUtil.writeEndElement(writer);
+        StaxUtil.flush(writer);
+    }
+
+    /**
+     * Checks that the given element is indeed a Keycloak extension {@code KeyInfo} element and
+     * returns a content of {@code MessageSigningKeyId} attribute in the given element.
+     * @param element Element to obtain the key info from.
+     * @return {@code null} if the element is unknown or there is {@code MessageSigningKeyId} attribute unset,
+     *   value of the {@code MessageSigningKeyId} attribute otherwise.
+     */
+    public static String getMessageSigningKeyIdFromElement(Element element) {
+        if (Objects.equals(element.getNamespaceURI(), NS_URI) &&
+          Objects.equals(element.getLocalName(), KC_KEY_INFO_ELEMENT_NAME) &&
+          element.hasAttribute(KEY_ID_ATTRIBUTE_NAME)) {
+            return element.getAttribute(KEY_ID_ATTRIBUTE_NAME);
+        }
+
+        return null;
+    }
+
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/SignatureUtilTransferObject.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/SignatureUtilTransferObject.java
index 19924e9..f8181fe 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/SignatureUtilTransferObject.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/SignatureUtilTransferObject.java
@@ -32,6 +32,9 @@ public class SignatureUtilTransferObject {
     private X509Certificate x509Certificate;
 
     private Document documentToBeSigned;
+
+    private String keyId;
+
     private KeyPair keyPair;
 
     private Node nextSibling;
@@ -111,4 +114,12 @@ public class SignatureUtilTransferObject {
     public void setX509Certificate(X509Certificate x509Certificate) {
         this.x509Certificate = x509Certificate;
     }
+
+    public String getKeyId() {
+        return keyId;
+    }
+
+    public void setKeyId(String keyId) {
+        this.keyId = keyId;
+    }
 }
\ No newline at end of file
diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java
index 98635b7..193af19 100755
--- a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java
+++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLSignatureUtil.java
@@ -54,8 +54,6 @@ import javax.xml.crypto.dsig.dom.DOMSignContext;
 import javax.xml.crypto.dsig.dom.DOMValidateContext;
 import javax.xml.crypto.dsig.keyinfo.KeyInfo;
 import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
-import javax.xml.crypto.dsig.keyinfo.KeyValue;
-import javax.xml.crypto.dsig.keyinfo.X509Data;
 import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
 import javax.xml.crypto.dsig.spec.TransformParameterSpec;
 import javax.xml.namespace.QName;
@@ -69,6 +67,7 @@ import java.io.OutputStream;
 import java.security.GeneralSecurityException;
 import java.security.Key;
 import java.security.KeyException;
+import java.security.KeyManagementException;
 import java.security.KeyPair;
 import java.security.NoSuchProviderException;
 import java.security.PrivateKey;
@@ -79,7 +78,16 @@ import java.security.interfaces.DSAPublicKey;
 import java.security.interfaces.RSAPublicKey;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.LinkedList;
 import java.util.List;
+import javax.xml.crypto.AlgorithmMethod;
+import javax.xml.crypto.KeySelector;
+import javax.xml.crypto.KeySelectorException;
+import javax.xml.crypto.KeySelectorResult;
+import javax.xml.crypto.XMLCryptoContext;
+import javax.xml.crypto.dsig.keyinfo.KeyName;
+import org.keycloak.rotation.KeyLocator;
+import org.keycloak.saml.processing.api.util.KeyInfoTools;
 
 /**
  * Utility for XML Signature <b>Note:</b> You can change the canonicalization method type by using the system property
@@ -105,15 +113,66 @@ public class XMLSignatureUtil {
 
     ;
 
-    private static String canonicalizationMethodType = CanonicalizationMethod.EXCLUSIVE;
-
-    private static XMLSignatureFactory fac = getXMLSignatureFactory();
+    private static final XMLSignatureFactory fac = getXMLSignatureFactory();
 
     /**
      * By default, we include the keyinfo in the signature
      */
     private static boolean includeKeyInfoInSignature = true;
 
+    private static class KeySelectorUtilizingKeyNameHint extends KeySelector {
+
+        private final KeyLocator locator;
+
+        private boolean keyLocated = false;
+
+        private String keyName = null;
+
+        public KeySelectorUtilizingKeyNameHint(KeyLocator locator) {
+            this.locator = locator;
+        }
+
+        @Override
+        public KeySelectorResult select(KeyInfo keyInfo, KeySelector.Purpose purpose, AlgorithmMethod method, XMLCryptoContext context) throws KeySelectorException {
+            try {
+                KeyName keyNameEl = KeyInfoTools.getKeyName(keyInfo);
+                this.keyName = keyNameEl == null ? null : keyNameEl.getName();
+                final Key key = locator.getKey(keyName);
+                this.keyLocated = key != null;
+                return new KeySelectorResult() {
+                    @Override public Key getKey() {
+                        return key;
+                    }
+                };
+            } catch (KeyManagementException ex) {
+                throw new KeySelectorException(ex);
+            }
+
+        }
+
+        private boolean wasKeyLocated() {
+            return this.keyLocated;
+        }
+    }
+
+    private static class KeySelectorPresetKey extends KeySelector {
+
+        private final Key key;
+
+        public KeySelectorPresetKey(Key key) {
+            this.key = key;
+        }
+
+        @Override
+        public KeySelectorResult select(KeyInfo keyInfo, KeySelector.Purpose purpose, AlgorithmMethod method, XMLCryptoContext context) {
+            return new KeySelectorResult() {
+                @Override public Key getKey() {
+                    return key;
+                }
+            };
+        }
+    }
+
     private static XMLSignatureFactory getXMLSignatureFactory() {
         XMLSignatureFactory xsf = null;
 
@@ -157,7 +216,7 @@ public class XMLSignatureUtil {
      * @throws MarshalException
      * @throws GeneralSecurityException
      */
-    public static Document sign(Document doc, Node nodeToBeSigned, KeyPair keyPair, String digestMethod,
+    public static Document sign(Document doc, Node nodeToBeSigned, String keyId, KeyPair keyPair, String digestMethod,
                                 String signatureMethod, String referenceURI, X509Certificate x509Certificate,
                                 String canonicalizationMethodType) throws ParserConfigurationException, GeneralSecurityException,
             MarshalException, XMLSignatureException {
@@ -179,7 +238,7 @@ public class XMLSignatureUtil {
         if (!referenceURI.isEmpty()) {
             propagateIDAttributeSetup(nodeToBeSigned, newDoc.getDocumentElement());
         }
-        newDoc = sign(newDoc, keyPair, digestMethod, signatureMethod, referenceURI, x509Certificate, canonicalizationMethodType);
+        newDoc = sign(newDoc, keyId, keyPair, digestMethod, signatureMethod, referenceURI, x509Certificate, canonicalizationMethodType);
 
         // if the signed element is a SAMLv2.0 assertion we need to move the signature element to the position
         // specified in the schema (before the assertion subject element).
@@ -220,10 +279,10 @@ public class XMLSignatureUtil {
      * @throws MarshalException
      * @throws XMLSignatureException
      */
-    public static void sign(Element elementToSign, Node nextSibling, KeyPair keyPair, String digestMethod,
+    public static void sign(Element elementToSign, Node nextSibling, String keyId, KeyPair keyPair, String digestMethod,
                             String signatureMethod, String referenceURI, String canonicalizationMethodType)
             throws GeneralSecurityException, MarshalException, XMLSignatureException {
-        sign(elementToSign, nextSibling, keyPair, digestMethod, signatureMethod, referenceURI, null, canonicalizationMethodType);
+        sign(elementToSign, nextSibling, keyId, keyPair, digestMethod, signatureMethod, referenceURI, null, canonicalizationMethodType);
     }
 
     /**
@@ -242,7 +301,7 @@ public class XMLSignatureUtil {
      * @throws XMLSignatureException
      * @since 2.5.0
      */
-    public static void sign(Element elementToSign, Node nextSibling, KeyPair keyPair, String digestMethod,
+    public static void sign(Element elementToSign, Node nextSibling, String keyId, KeyPair keyPair, String digestMethod,
                             String signatureMethod, String referenceURI, X509Certificate x509Certificate, String canonicalizationMethodType)
             throws GeneralSecurityException, MarshalException, XMLSignatureException {
         PrivateKey signingKey = keyPair.getPrivate();
@@ -250,7 +309,7 @@ public class XMLSignatureUtil {
 
         DOMSignContext dsc = new DOMSignContext(signingKey, elementToSign, nextSibling);
 
-        signImpl(dsc, digestMethod, signatureMethod, referenceURI, publicKey, x509Certificate, canonicalizationMethodType);
+        signImpl(dsc, digestMethod, signatureMethod, referenceURI, keyId, publicKey, x509Certificate, canonicalizationMethodType);
     }
 
     /**
@@ -284,9 +343,9 @@ public class XMLSignatureUtil {
      * @throws XMLSignatureException
      * @throws MarshalException
      */
-    public static Document sign(Document doc, KeyPair keyPair, String digestMethod, String signatureMethod, String referenceURI, String canonicalizationMethodType)
+    public static Document sign(Document doc, String keyId, KeyPair keyPair, String digestMethod, String signatureMethod, String referenceURI, String canonicalizationMethodType)
             throws GeneralSecurityException, MarshalException, XMLSignatureException {
-        return sign(doc, keyPair, digestMethod, signatureMethod, referenceURI, null, canonicalizationMethodType);
+        return sign(doc, keyId, keyPair, digestMethod, signatureMethod, referenceURI, null, canonicalizationMethodType);
     }
 
     /**
@@ -304,7 +363,7 @@ public class XMLSignatureUtil {
      * @throws MarshalException
      * @since 2.5.0
      */
-    public static Document sign(Document doc, KeyPair keyPair, String digestMethod, String signatureMethod, String referenceURI,
+    public static Document sign(Document doc, String keyId, KeyPair keyPair, String digestMethod, String signatureMethod, String referenceURI,
                                 X509Certificate x509Certificate, String canonicalizationMethodType)
             throws GeneralSecurityException, MarshalException, XMLSignatureException {
         logger.trace("Document to be signed=" + DocumentUtil.asString(doc));
@@ -313,7 +372,7 @@ public class XMLSignatureUtil {
 
         DOMSignContext dsc = new DOMSignContext(signingKey, doc.getDocumentElement());
 
-        signImpl(dsc, digestMethod, signatureMethod, referenceURI, publicKey, x509Certificate, canonicalizationMethodType);
+        signImpl(dsc, digestMethod, signatureMethod, referenceURI, keyId, publicKey, x509Certificate, canonicalizationMethodType);
 
         return doc;
     }
@@ -331,6 +390,7 @@ public class XMLSignatureUtil {
     public static Document sign(SignatureUtilTransferObject dto, String canonicalizationMethodType) throws GeneralSecurityException, MarshalException,
             XMLSignatureException {
         Document doc = dto.getDocumentToBeSigned();
+        String keyId = dto.getKeyId();
         KeyPair keyPair = dto.getKeyPair();
         Node nextSibling = dto.getNextSibling();
         String digestMethod = dto.getDigestMethod();
@@ -344,13 +404,14 @@ public class XMLSignatureUtil {
 
         DOMSignContext dsc = new DOMSignContext(signingKey, doc.getDocumentElement(), nextSibling);
 
-        signImpl(dsc, digestMethod, signatureMethod, referenceURI, publicKey, dto.getX509Certificate(), canonicalizationMethodType);
+        signImpl(dsc, digestMethod, signatureMethod, referenceURI, keyId, publicKey, dto.getX509Certificate(), canonicalizationMethodType);
 
         return doc;
     }
 
     /**
-     * Validate a signed document with the given public key
+     * Validate a signed document with the given public key. All elements that contain a Signature are checked,
+     * this way both assertions and the containing document are verified when signed.
      *
      * @param signedDoc
      * @param publicKey
@@ -361,7 +422,7 @@ public class XMLSignatureUtil {
      * @throws XMLSignatureException
      */
     @SuppressWarnings("unchecked")
-    public static boolean validate(Document signedDoc, Key publicKey) throws MarshalException, XMLSignatureException {
+    public static boolean validate(Document signedDoc, final KeyLocator locator) throws MarshalException, XMLSignatureException {
         if (signedDoc == null)
             throw logger.nullArgumentError("Signed Document");
 
@@ -374,7 +435,7 @@ public class XMLSignatureUtil {
             return false;
         }
 
-        if (publicKey == null)
+        if (locator == null)
             throw logger.nullValueError("Public Key");
 
         int signedAssertions = 0;
@@ -390,24 +451,7 @@ public class XMLSignatureUtil {
                 }
             }
 
-            DOMValidateContext valContext = new DOMValidateContext(publicKey, nl.item(i));
-            XMLSignature signature = fac.unmarshalXMLSignature(valContext);
-
-            boolean coreValidity = signature.validate(valContext);
-
-            if (!coreValidity) {
-                if (logger.isTraceEnabled()) {
-                    boolean sv = signature.getSignatureValue().validate(valContext);
-                    logger.trace("Signature validation status: " + sv);
-
-                    List<Reference> references = signature.getSignedInfo().getReferences();
-                    for (Reference ref : references) {
-                        logger.trace("[Ref id=" + ref.getId() + ":uri=" + ref.getURI() + "]validity status:" + ref.validate(valContext));
-                    }
-                }
-
-                return false;
-            }
+            if (! validateSingleNode(signatureNode, locator)) return false;
         }
 
         NodeList assertions = signedDoc.getElementsByTagNameNS(assertionNameSpaceUri, JBossSAMLConstants.ASSERTION.get());
@@ -423,6 +467,62 @@ public class XMLSignatureUtil {
         return true;
     }
 
+    private static boolean validateSingleNode(Node signatureNode, final KeyLocator locator) throws MarshalException, XMLSignatureException {
+        KeySelectorUtilizingKeyNameHint sel = new KeySelectorUtilizingKeyNameHint(locator);
+        try {
+            if (validateUsingKeySelector(signatureNode, sel)) {
+                return true;
+            }
+            if (sel.wasKeyLocated()) {
+                return false;
+            }
+        } catch (XMLSignatureException ex) { // pass through MarshalException
+            logger.debug("Verification failed for key " + sel.keyName + ": " + ex);
+            logger.trace(ex);
+        }
+
+        logger.trace("Could not validate signature using ds:KeyInfo/ds:KeyName hint.");
+
+        if (locator instanceof Iterable) {
+            Iterable<Key> availableKeys = (Iterable<Key>) locator;
+
+            logger.trace("Trying hard to validate XML signature using all available keys.");
+
+            for (Key key : availableKeys) {
+                try {
+                    if (validateUsingKeySelector(signatureNode, new KeySelectorPresetKey(key))) {
+                        return true;
+                    }
+                } catch (XMLSignatureException ex) { // pass through MarshalException
+                    logger.debug("Verification failed: " + ex);
+                    logger.trace(ex);
+                }
+            }
+        }
+
+        return false;
+    }
+
+    private static boolean validateUsingKeySelector(Node signatureNode, KeySelector validationKeySelector) throws XMLSignatureException, MarshalException {
+        DOMValidateContext valContext = new DOMValidateContext(validationKeySelector, signatureNode);
+        XMLSignature signature = fac.unmarshalXMLSignature(valContext);
+        boolean coreValidity = signature.validate(valContext);
+        
+        if (! coreValidity) {
+            if (logger.isTraceEnabled()) {
+                boolean sv = signature.getSignatureValue().validate(valContext);
+                logger.trace("Signature validation status: " + sv);
+
+                List<Reference> references = signature.getSignedInfo().getReferences();
+                for (Reference ref : references) {
+                    logger.trace("[Ref id=" + ref.getId() + ":uri=" + ref.getURI() + "]validity status:" + ref.validate(valContext));
+                }
+            }
+        }
+
+        return coreValidity;
+    }
+
     /**
      * Marshall a SignatureType to output stream
      *
@@ -594,7 +694,7 @@ public class XMLSignatureUtil {
         throw logger.unsupportedType(key.toString());
     }
 
-    private static void signImpl(DOMSignContext dsc, String digestMethod, String signatureMethod, String referenceURI, PublicKey publicKey,
+    private static void signImpl(DOMSignContext dsc, String digestMethod, String signatureMethod, String referenceURI, String keyId, PublicKey publicKey,
                                  X509Certificate x509Certificate, String canonicalizationMethodType)
             throws GeneralSecurityException, MarshalException, XMLSignatureException {
         dsc.setDefaultNamespacePrefix("dsig");
@@ -603,7 +703,7 @@ public class XMLSignatureUtil {
         Transform transform1 = fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null);
         Transform transform2 = fac.newTransform("http://www.w3.org/2001/10/xml-exc-c14n#", (TransformParameterSpec) null);
 
-        List<Transform> transformList = new ArrayList<Transform>();
+        List<Transform> transformList = new ArrayList<>();
         transformList.add(transform1);
         transformList.add(transform2);
 
@@ -616,37 +716,34 @@ public class XMLSignatureUtil {
         SignatureMethod signatureMethodObj = fac.newSignatureMethod(signatureMethod, null);
         SignedInfo si = fac.newSignedInfo(canonicalizationMethod, signatureMethodObj, referenceList);
 
-        KeyInfo ki = null;
+        KeyInfo ki;
         if (includeKeyInfoInSignature) {
-            ki = createKeyInfo(publicKey, x509Certificate);
+            ki = createKeyInfo(keyId, publicKey, x509Certificate);
+        } else {
+            ki = createKeyInfo(keyId, null, null);
         }
         XMLSignature signature = fac.newXMLSignature(si, ki);
 
         signature.sign(dsc);
     }
 
-    private static KeyInfo createKeyInfo(PublicKey publicKey, X509Certificate x509Certificate) throws KeyException {
+    private static KeyInfo createKeyInfo(String keyId, PublicKey publicKey, X509Certificate x509Certificate) throws KeyException {
         KeyInfoFactory keyInfoFactory = fac.getKeyInfoFactory();
-        KeyInfo keyInfo = null;
-        KeyValue keyValue = null;
-        //Just with public key
-        if (publicKey != null) {
-            keyValue = keyInfoFactory.newKeyValue(publicKey);
-            keyInfo = keyInfoFactory.newKeyInfo(Collections.singletonList(keyValue));
+
+        List<Object> items = new LinkedList<>();
+
+        if (keyId != null) {
+            items.add(keyInfoFactory.newKeyName(keyId));
         }
-        if (x509Certificate != null) {
-            List x509list = new ArrayList();
 
-            x509list.add(x509Certificate);
-            X509Data x509Data = keyInfoFactory.newX509Data(x509list);
-            List items = new ArrayList();
+        if (x509Certificate != null) {
+            items.add(keyInfoFactory.newX509Data(Collections.singletonList(x509Certificate)));
+        }
 
-            items.add(x509Data);
-            if (keyValue != null) {
-                items.add(keyValue);
-            }
-            keyInfo = keyInfoFactory.newKeyInfo(items);
+        if (publicKey != null) {
+            items.add(keyInfoFactory.newKeyValue(publicKey));
         }
-        return keyInfo;
+
+        return keyInfoFactory.newKeyInfo(items);
     }
 }
\ No newline at end of file
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java
index 302237e..ec4fc28 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2AuthnRequestBuilder.java
@@ -25,15 +25,19 @@ import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
 import org.w3c.dom.Document;
 
 import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
 
 /**
  * @author pedroigor
  */
-public class SAML2AuthnRequestBuilder {
+public class SAML2AuthnRequestBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2AuthnRequestBuilder> {
 
     private final AuthnRequestType authnRequestType;
     protected String destination;
     protected String issuer;
+    protected final List<NodeGenerator> extensions = new LinkedList<>();
 
     public SAML2AuthnRequestBuilder destination(String destination) {
         this.destination = destination;
@@ -45,6 +49,12 @@ public class SAML2AuthnRequestBuilder {
         return this;
     }
 
+    @Override
+    public SAML2AuthnRequestBuilder addExtension(NodeGenerator extension) {
+        this.extensions.add(extension);
+        return this;
+    }
+
     public SAML2AuthnRequestBuilder() {
         try {
             this.authnRequestType = new AuthnRequestType(IDGenerator.create("ID_"), XMLTimeUtil.getIssueInstant());
@@ -90,6 +100,14 @@ public class SAML2AuthnRequestBuilder {
 
             authnRequestType.setDestination(URI.create(this.destination));
 
+            if (! this.extensions.isEmpty()) {
+                ExtensionsType extensionsType = new ExtensionsType();
+                for (NodeGenerator extension : this.extensions) {
+                    extensionsType.addExtension(extension);
+                }
+                authnRequestType.setExtensions(extensionsType);
+            }
+
             return new SAML2Request().convert(authnRequestType);
         } catch (Exception e) {
             throw new RuntimeException("Could not convert " + authnRequestType + " to a document.", e);
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java
index 99d1c1f..6da6799 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2ErrorResponseBuilder.java
@@ -17,7 +17,10 @@
 
 package org.keycloak.saml;
 
+import java.util.LinkedList;
+import java.util.List;
 import org.keycloak.dom.saml.v2.assertion.NameIDType;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
 import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
 import org.keycloak.saml.common.exceptions.ConfigurationException;
 import org.keycloak.saml.common.exceptions.ParsingException;
@@ -32,11 +35,12 @@ import org.w3c.dom.Document;
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class SAML2ErrorResponseBuilder {
+public class SAML2ErrorResponseBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2ErrorResponseBuilder> {
 
     protected String status;
     protected String destination;
     protected String issuer;
+    protected final List<NodeGenerator> extensions = new LinkedList<>();
 
     public SAML2ErrorResponseBuilder status(String status) {
         this.status = status;
@@ -53,6 +57,11 @@ public class SAML2ErrorResponseBuilder {
         return this;
     }
 
+    @Override
+    public SAML2ErrorResponseBuilder addExtension(NodeGenerator extension) {
+        this.extensions.add(extension);
+        return this;
+    }
 
     public Document buildDocument() throws ProcessingException {
 
@@ -66,6 +75,14 @@ public class SAML2ErrorResponseBuilder {
             statusResponse.setIssuer(issuer);
             statusResponse.setDestination(destination);
 
+            if (! this.extensions.isEmpty()) {
+                ExtensionsType extensionsType = new ExtensionsType();
+                for (NodeGenerator extension : this.extensions) {
+                    extensionsType.addExtension(extension);
+                }
+                statusResponse.setExtensions(extensionsType);
+            }
+
             SAML2Response saml2Response = new SAML2Response();
             return saml2Response.convert(statusResponse);
         } catch (ConfigurationException e) {
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2LoginResponseBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2LoginResponseBuilder.java
index 2386edb..17dafc7 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2LoginResponseBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2LoginResponseBuilder.java
@@ -39,6 +39,9 @@ import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
 import org.w3c.dom.Document;
 
 import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
 
 import static org.keycloak.saml.common.util.StringUtil.isNotNull;
 
@@ -49,7 +52,7 @@ import static org.keycloak.saml.common.util.StringUtil.isNotNull;
  *
  * @author bburke@redhat.com
 */
-public class SAML2LoginResponseBuilder {
+public class SAML2LoginResponseBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2LoginResponseBuilder> {
     protected static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
 
     protected String destination;
@@ -64,6 +67,7 @@ public class SAML2LoginResponseBuilder {
     protected String authMethod;
     protected String requestIssuer;
     protected String sessionIndex;
+    protected final List<NodeGenerator> extensions = new LinkedList<>();
 
 
     public SAML2LoginResponseBuilder sessionIndex(String sessionIndex) {
@@ -136,6 +140,12 @@ public class SAML2LoginResponseBuilder {
         return this;
     }
 
+    @Override
+    public SAML2LoginResponseBuilder addExtension(NodeGenerator extension) {
+        this.extensions.add(extension);
+        return this;
+    }
+
     public Document buildDocument(ResponseType responseType) throws ConfigurationException, ProcessingException {
         Document samlResponseDocument = null;
 
@@ -207,6 +217,14 @@ public class SAML2LoginResponseBuilder {
             assertion.addStatement(authnStatement);
         }
 
+        if (! this.extensions.isEmpty()) {
+            ExtensionsType extensionsType = new ExtensionsType();
+            for (NodeGenerator extension : this.extensions) {
+                extensionsType.addExtension(extension);
+            }
+            responseType.setExtensions(extensionsType);
+        }
+
         return responseType;
     }
 
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java
index 99b1cf8..d0e81ba 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutRequestBuilder.java
@@ -27,18 +27,22 @@ import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
 import org.w3c.dom.Document;
 
 import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class SAML2LogoutRequestBuilder {
+public class SAML2LogoutRequestBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2LogoutRequestBuilder> {
     protected String userPrincipal;
     protected String userPrincipalFormat;
     protected String sessionIndex;
     protected long assertionExpiration;
     protected String destination;
     protected String issuer;
+    protected final List<NodeGenerator> extensions = new LinkedList<>();
 
     public SAML2LogoutRequestBuilder destination(String destination) {
         this.destination = destination;
@@ -50,6 +54,12 @@ public class SAML2LogoutRequestBuilder {
         return this;
     }
 
+    @Override
+    public SAML2LogoutRequestBuilder addExtension(NodeGenerator extension) {
+        this.extensions.add(extension);
+        return this;
+    }
+
     /**
      * Length of time in seconds the assertion is valid for
      * See SAML core specification 2.5.1.2 NotOnOrAfter
@@ -99,6 +109,15 @@ public class SAML2LogoutRequestBuilder {
 
         if (assertionExpiration > 0) lort.setNotOnOrAfter(XMLTimeUtil.add(lort.getIssueInstant(), assertionExpiration * 1000));
         lort.setDestination(URI.create(destination));
+
+        if (! this.extensions.isEmpty()) {
+            ExtensionsType extensionsType = new ExtensionsType();
+            for (NodeGenerator extension : this.extensions) {
+                extensionsType.addExtension(extension);
+            }
+            lort.setExtensions(extensionsType);
+        }
+
         return lort;
     }
 }
diff --git a/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutResponseBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutResponseBuilder.java
index c00a4d4..8050e81 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutResponseBuilder.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SAML2LogoutResponseBuilder.java
@@ -31,16 +31,20 @@ import org.keycloak.saml.processing.core.saml.v2.util.XMLTimeUtil;
 import org.w3c.dom.Document;
 
 import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
-public class SAML2LogoutResponseBuilder {
+public class SAML2LogoutResponseBuilder implements SamlProtocolExtensionsAwareBuilder<SAML2LogoutResponseBuilder> {
 
     protected String logoutRequestID;
     protected String destination;
     protected String issuer;
+    protected final List<NodeGenerator> extensions = new LinkedList<>();
 
     public SAML2LogoutResponseBuilder logoutRequestID(String logoutRequestID) {
         this.logoutRequestID = logoutRequestID;
@@ -57,6 +61,11 @@ public class SAML2LogoutResponseBuilder {
         return this;
     }
 
+    @Override
+    public SAML2LogoutResponseBuilder addExtension(NodeGenerator extension) {
+        this.extensions.add(extension);
+        return this;
+    }
 
     public Document buildDocument() throws ProcessingException {
         Document samlResponse = null;
@@ -77,6 +86,14 @@ public class SAML2LogoutResponseBuilder {
             statusResponse.setIssuer(issuer);
             statusResponse.setDestination(destination);
 
+            if (! this.extensions.isEmpty()) {
+                ExtensionsType extensionsType = new ExtensionsType();
+                for (NodeGenerator extension : this.extensions) {
+                    extensionsType.addExtension(extension);
+                }
+                statusResponse.setExtensions(extensionsType);
+            }
+
             SAML2Response saml2Response = new SAML2Response();
             samlResponse = saml2Response.convert(statusResponse);
         } catch (ConfigurationException e) {
diff --git a/saml-core/src/main/java/org/keycloak/saml/SamlProtocolExtensionsAwareBuilder.java b/saml-core/src/main/java/org/keycloak/saml/SamlProtocolExtensionsAwareBuilder.java
new file mode 100644
index 0000000..2192df6
--- /dev/null
+++ b/saml-core/src/main/java/org/keycloak/saml/SamlProtocolExtensionsAwareBuilder.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.saml;
+
+import javax.xml.stream.XMLStreamWriter;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+
+/**
+ * Implementations of this interface are builders that can register &lt;samlp:Extensions&gt;
+ * content providers.
+ *
+ * @author hmlnarik
+ */
+public interface SamlProtocolExtensionsAwareBuilder<T> {
+
+    public interface NodeGenerator {
+        /**
+         * Generate contents of the &lt;samlp:Extensions&gt; tag. When this method is invoked,
+         * the writer has already emitted the &lt;samlp:Extensions&gt; start tag.
+         *
+         * @param writer Writer to use for producing XML output
+         * @throws ProcessingException If any exception fails
+         */
+        void write(XMLStreamWriter writer) throws ProcessingException;
+    }
+
+    /**
+     * Adds a given node subtree as a SAML protocol extension into the SAML protocol message.
+     *
+     * @param extension
+     * @return
+     */
+    T addExtension(NodeGenerator extension);
+}
diff --git a/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java b/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java
index e6c10af..9a28137 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java
@@ -22,21 +22,14 @@ package org.keycloak.saml;
  * @version $Revision: 1 $
  */
 public class SPMetadataDescriptor {
-    public static String getSPDescriptor(String binding, String assertionEndpoint, String logoutEndpoint, boolean wantAuthnRequestsSigned, String entityId, String nameIDPolicyFormat, String certificatePem) {
+
+    public static String getSPDescriptor(String binding, String assertionEndpoint, String logoutEndpoint, boolean wantAuthnRequestsSigned, String entityId, String nameIDPolicyFormat, String signingCerts) {
         String descriptor =
                 "<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"" + entityId + "\">\n" +
                 "    <SPSSODescriptor AuthnRequestsSigned=\"" + wantAuthnRequestsSigned + "\"\n" +
                 "            protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol http://schemas.xmlsoap.org/ws/2003/07/secext\">\n";
-        if (wantAuthnRequestsSigned) {
-            descriptor +=
-                    "        <KeyDescriptor use=\"signing\">\n" +
-                            "            <dsig:KeyInfo xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">\n" +
-                            "                <dsig:X509Data>\n" +
-                            "                    <dsig:X509Certificate>\n" + certificatePem + "\n" +
-                            "                    </dsig:X509Certificate>\n" +
-                            "                </dsig:X509Data>\n" +
-                            "            </dsig:KeyInfo>\n" +
-                            "        </KeyDescriptor>\n";
+        if (wantAuthnRequestsSigned  && signingCerts != null) {
+            descriptor += signingCerts;
         }
         descriptor +=
                 "        <SingleLogoutService Binding=\"" + binding + "\" Location=\"" + logoutEndpoint + "\"/>\n" +
@@ -44,10 +37,34 @@ public class SPMetadataDescriptor {
                 "        </NameIDFormat>\n" +
                 "        <AssertionConsumerService\n" +
                 "                Binding=\"" + binding + "\" Location=\"" + assertionEndpoint + "\"\n" +
-                "                index=\"1\" isDefault=\"true\" />\n";
-        descriptor +=
+                "                index=\"1\" isDefault=\"true\" />\n" +
                 "    </SPSSODescriptor>\n" +
                 "</EntityDescriptor>\n";
         return descriptor;
     }
+
+    public static String xmlKeyInfo(String indentation, String keyId, String pemEncodedCertificate, String purpose, boolean declareDSigNamespace) {
+        if (pemEncodedCertificate == null) {
+            return "";
+        }
+
+        StringBuilder target = new StringBuilder()
+          .append(indentation).append("<KeyDescriptor use=\"").append(purpose).append("\">\n")
+          .append(indentation).append("  <dsig:KeyInfo").append(declareDSigNamespace ? " xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">\n" : ">\n");
+
+        if (keyId != null) {
+            target.append(indentation).append("    <dsig:KeyName>").append(keyId).append("</dsig:KeyName>\n");
+        }
+
+        target
+          .append(indentation).append("    <dsig:X509Data>\n")
+          .append(indentation).append("      <dsig:X509Certificate>").append(pemEncodedCertificate).append("</dsig:X509Certificate>\n")
+          .append(indentation).append("    </dsig:X509Data>\n")
+          .append(indentation).append("  </dsig:KeyInfo>\n")
+          .append(indentation).append("</KeyDescriptor>\n")
+        ;
+
+        return target.toString();
+    }
+
 }
diff --git a/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java b/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java
new file mode 100644
index 0000000..ad150e9
--- /dev/null
+++ b/saml-core/src/test/java/org/keycloak/saml/processing/core/parsers/saml/SAMLParserTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.saml.processing.core.parsers.saml;
+
+import java.io.InputStream;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.hamcrest.CoreMatchers.*;
+import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
+import org.keycloak.dom.saml.v2.protocol.ResponseType;
+import org.w3c.dom.Element;
+
+/**
+ * Test class for SAML parser.
+ *
+ * TODO: Add further tests.
+ *
+ * @author hmlnarik
+ */
+public class SAMLParserTest {
+
+    @Test
+    public void testSaml20EncryptedAssertionsSignedReceivedWithRedirectBinding() throws Exception {
+        InputStream st = SAMLParserTest.class.getResourceAsStream("saml20-encrypted-signed-redirect-response.xml");
+        SAMLParser parser = new SAMLParser();
+
+        Object parsedObject = parser.parse(st);
+        assertThat(parsedObject, instanceOf(ResponseType.class));
+        
+        ResponseType resp = (ResponseType) parsedObject;
+        assertThat(resp.getSignature(), nullValue());
+        assertThat(resp.getConsent(), nullValue());
+        assertThat(resp.getIssuer(), not(nullValue()));
+        assertThat(resp.getIssuer().getValue(), is("http://localhost:8081/auth/realms/saml-demo"));
+
+        assertThat(resp.getExtensions(), not(nullValue()));
+        assertThat(resp.getExtensions().getAny().size(), is(1));
+        assertThat(resp.getExtensions().getAny().get(0), instanceOf(Element.class));
+        Element el = (Element) resp.getExtensions().getAny().get(0);
+        assertThat(el.getLocalName(), is("KeyInfo"));
+        assertThat(el.getNamespaceURI(), is("urn:keycloak:ext:key:1.0"));
+        assertThat(el.hasAttribute("MessageSigningKeyId"), is(true));
+        assertThat(el.getAttribute("MessageSigningKeyId"), is("FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE"));
+
+        assertThat(resp.getAssertions(), not(nullValue()));
+        assertThat(resp.getAssertions().size(), is(1));
+    }
+
+    @Test
+    public void testSaml20EncryptedAssertionsSignedTwoExtensionsReceivedWithRedirectBinding() throws Exception {
+        Element el;
+
+        InputStream st = SAMLParserTest.class.getResourceAsStream("saml20-encrypted-signed-redirect-response-two-extensions.xml");
+        SAMLParser parser = new SAMLParser();
+
+        Object parsedObject = parser.parse(st);
+        assertThat(parsedObject, instanceOf(ResponseType.class));
+
+        ResponseType resp = (ResponseType) parsedObject;
+        assertThat(resp.getSignature(), nullValue());
+        assertThat(resp.getConsent(), nullValue());
+        assertThat(resp.getIssuer(), not(nullValue()));
+        assertThat(resp.getIssuer().getValue(), is("http://localhost:8081/auth/realms/saml-demo"));
+
+        assertThat(resp.getExtensions(), not(nullValue()));
+        assertThat(resp.getExtensions().getAny().size(), is(2));
+        assertThat(resp.getExtensions().getAny().get(0), instanceOf(Element.class));
+        el = (Element) resp.getExtensions().getAny().get(0);
+        assertThat(el.getLocalName(), is("KeyInfo"));
+        assertThat(el.getNamespaceURI(), is("urn:keycloak:ext:key:1.0"));
+        assertThat(el.hasAttribute("MessageSigningKeyId"), is(true));
+        assertThat(el.getAttribute("MessageSigningKeyId"), is("FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE"));
+        assertThat(resp.getExtensions().getAny().get(1), instanceOf(Element.class));
+        el = (Element) resp.getExtensions().getAny().get(1);
+        assertThat(el.getLocalName(), is("ever"));
+        assertThat(el.getNamespaceURI(), is("urn:keycloak:ext:what:1.0"));
+        assertThat(el.hasAttribute("what"), is(true));
+        assertThat(el.getAttribute("what"), is("ever"));
+
+        assertThat(resp.getAssertions(), not(nullValue()));
+        assertThat(resp.getAssertions().size(), is(1));
+    }
+
+    @Test
+    public void testSaml20PostLogoutRequest() throws Exception {
+        InputStream st = SAMLParserTest.class.getResourceAsStream("saml20-signed-logout-request.xml");
+        SAMLParser parser = new SAMLParser();
+
+        Object parsedObject = parser.parse(st);
+        assertThat(parsedObject, instanceOf(LogoutRequestType.class));
+
+    }
+}
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response.xml
new file mode 100644
index 0000000..d8d4c15
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response.xml
@@ -0,0 +1,29 @@
+<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Destination="http://localhost:8080/sales-post-enc/saml" ID="ID_0b43d444-d1a8-44a5-8caf-38e176489e1f" InResponseTo="ID_223d3591-22fb-4b3c-9e38-4719293b2d94" IssueInstant="2016-11-01T13:52:43.054Z" Version="2.0">
+    <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://localhost:8081/auth/realms/saml-demo</saml:Issuer>
+    <samlp:Extensions>
+        <kckey:KeyInfo xmlns:kckey="urn:keycloak:ext:key:1.0" MessageSigningKeyId="FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE"/>
+    </samlp:Extensions>
+    <samlp:Status>
+        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
+    </samlp:Status>
+    <saml:EncryptedAssertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
+        <xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Element">
+            <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+                <xenc:EncryptedKey xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
+                    <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
+                    <xenc:CipherData>
+                        <xenc:CipherValue>
+                            OkvZTx/ifYLef74rY0F9I8lbJaatgSEguo+zwh5JrYWcO09Ib2gtz5+z+67Is2+wk/OzKp154r8qAI5vY9AYvuXCslKL/wbcZ1UILL78F0T/iiUW3VpWy8Wvz5nezBFPRqot8WiFQykByjlBg1Z8XOts+uIdyqBBi/WjYeJGMaQ=
+                        </xenc:CipherValue>
+                    </xenc:CipherData>
+                </xenc:EncryptedKey>
+            </ds:KeyInfo>
+            <xenc:CipherData>
+                <xenc:CipherValue>
+                    RW2eu9nP2Ez9hfRlug9xC+kFfVF3HZpEb4kIFH33gmVbzrQjPk0l67uXkwRjC82FZZ482QnHCBIqNFlAryds/zTa6wdRvFmhQnIM6WxoAl8TM+e9h8MoKkalMc8J/Qfp+WQ7/XdmCg2pp9VvUZTK+g0+G4aGuL+S5+ssZq4rl9k7LrSYyp6vj+djgvISZiz5hPYJCN/WY/gWXfVuLHSpu4CmZt8D2APtT3ax1WmGcuzStAfTW8q3MFIDNV59hkpFmDb+gvyLNbZ95cDYxofiPXaC5cOTftnSBp68Ay1eienqdttEDo4fyakszdvq128KwXkH9azCg6sqLxli6B8l2xdq41MeuJO54VqmOhhLxwKy42NtnJvK/NkNwttH4yMwDPpPbC4vOKCXxT2r2F7jjvJQNB2VFv+oiUAWSSc3fGQcc2uNlx9YQVuzTmjqc7fXAWCGgYoogC8AeNWni204bnBoVpFrEo3gzuOe2fFsddJIclglmTH1hWf31FXUHDO2nl/lT4puQVTo+I+d6jpiV+qdp823NDntRxljRlUJO2AzSTXuIIGtF5q5KWyEi9Nj93BCWa1Llcddkn3ZEZMvDwR4MacwUj8G8hwoH73VvT3jAiakjSpNEIqYCzofeejdfN/gEuuAUfe8uNbTu+gBS+iP3QJe3Pc0Fs/lKJzd3frPNj7xb83wpOf865EQQoOozhnRIKKcMReSjakr/Px5NNooeiJcWEreDagQO2TbwTnHg1kCNG3BAXV/2lV3XBU4afZBoUfxAzYWFOl6xFCAPzhQCPL1SFJp1VRADY/1MU2Kaje5AZoJ4jjph8+yspxBvjic1vC1uYRGW8LWRind9w4eVhCm0LfPiFRCpP+jKPQOJzcNH580/nIMFXPHHnLKv/It7Qex1unDv/QjkuCFFHR6SWJm4WBrwDek+MyOIvgT6o878Cu0Ps472QpoYBQ+7l2WoylWdG1lHZV1UiHPj7PLHPNAL4rbbN3U88fS6N9OJHegQTfcX0i/1KPk4IN/5Z+/15dHI658BINjRvI/6O1QqaTVZkqM8ORcoGpn6BjAiz5rRhjWpOCwlmT+VzOAp3IqACURS1X+txjWE2mfVjlHLJsvyGRDLv1dUR3IeStDAEfsjR/ruRgn5XTFpYaccB/u//DJonJr5A+KFiLbYl+sbbSVAoQCAiAdxKdUpKPx7C473UJ2nYQGby5H5xwboa0Uj0SnJLYWdQ0jvVvzWpWFVWATc4UqnaxdoUDAmewrM6cSSIAmQBB34orCunFbriK9Z4efZ7gB9erQ1fpi3z/IjQBoTEpOUUIPW/qMAApIDPVM6UV9PumW7RL9zKEP5PuWJoGGnKbWGP/b9G4vMFiWMaSNHBYYMI6OLH4WJ3E+4QBGh2vjjfQ0gobhaLgIerIwCQFYEdl9KddAjaflUEFXal9fIQ8Bz9L3rDhQE5AGBZL6ULZmJe3GnkN6Cc+UWAGyD5zv2rsCG2lvR5ox4UE2mFi6nBJbC5Vj5m9Sz1l0QpRwUkH2kD2QQ5iV6nNmQOcU/mz7ulxluf8+FBJJimYVqK8UkJ6+W6j8Eft9Q8fTpEuEVLxqTWGgOAEUBf87RWDU+iF3A+AxFGsJLc5RC+5BKNTEDlV2qDCjHT7b5wqBKJ3FHulOih9EenlZiI51m6kg5yyxnMdbhasvSh6Az8Mp/4lFo/wSA/mXxNhBrEEmRhFiIE5yYUEYIj5F8fH+93tIuWQqyhXIwCntEOdSSmoei9EYFzj8deXcEzVf8y/N6HQErZcJjyg34caOsfRcJYoxEiCm4icA/btWhdjUNT02B20qnxGFndO4CRUQlyDqTbyVD8LRLK9/95L9+5v9zojLle8xQe30dsxKn7r9TTJH8QQai5iam9lU1ik50lwTKpZb18k4rNdO5cnnYoHzCXeCg38YZxyFt9G7um/MxlID5Qd5Ywq6thDzL7WxvanKeRhCuJ2MTVV0EoJxZKIj9Yv0Ars9mZHkoHoP0ikcW8d5ciDj1Onnbj+XDcYI3FZj0Y2vToZvYi/7eLWi8EnSjaIQrr/AHnrmZK1w3Uicd691U6r3Y0UdnzQEl4Ub/l1uhSaGAg2oEdDxkOdZ3Frvf/C4nTEBmunPlNvnJjVFssdeVVXKLBOZ5eRiJjasHUKnTeJVwolvd/dBI+ypfw1+5ae/0upxd9/gV1lbwX9N2yOwqbxz24cKXZWvOFBAGc3+gQFu8RrF6NAeQ96PlkuRsiNOKPPtJT3JNrLGvVKY8g==
+                </xenc:CipherValue>
+            </xenc:CipherData>
+        </xenc:EncryptedData>
+    </saml:EncryptedAssertion>
+</samlp:Response>
\ No newline at end of file
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response-two-extensions.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response-two-extensions.xml
new file mode 100644
index 0000000..94a6fdb
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-encrypted-signed-redirect-response-two-extensions.xml
@@ -0,0 +1,30 @@
+<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Destination="http://localhost:8080/sales-post-enc/saml" ID="ID_0b43d444-d1a8-44a5-8caf-38e176489e1f" InResponseTo="ID_223d3591-22fb-4b3c-9e38-4719293b2d94" IssueInstant="2016-11-01T13:52:43.054Z" Version="2.0">
+    <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://localhost:8081/auth/realms/saml-demo</saml:Issuer>
+    <samlp:Extensions>
+        <kckey:KeyInfo xmlns:kckey="urn:keycloak:ext:key:1.0" MessageSigningKeyId="FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE"/>
+        <what:ever xmlns:what="urn:keycloak:ext:what:1.0" what="ever"/>
+    </samlp:Extensions>
+    <samlp:Status>
+        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
+    </samlp:Status>
+    <saml:EncryptedAssertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
+        <xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Element">
+            <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+                <xenc:EncryptedKey xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
+                    <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
+                    <xenc:CipherData>
+                        <xenc:CipherValue>
+                            OkvZTx/ifYLef74rY0F9I8lbJaatgSEguo+zwh5JrYWcO09Ib2gtz5+z+67Is2+wk/OzKp154r8qAI5vY9AYvuXCslKL/wbcZ1UILL78F0T/iiUW3VpWy8Wvz5nezBFPRqot8WiFQykByjlBg1Z8XOts+uIdyqBBi/WjYeJGMaQ=
+                        </xenc:CipherValue>
+                    </xenc:CipherData>
+                </xenc:EncryptedKey>
+            </ds:KeyInfo>
+            <xenc:CipherData>
+                <xenc:CipherValue>
+                    RW2eu9nP2Ez9hfRlug9xC+kFfVF3HZpEb4kIFH33gmVbzrQjPk0l67uXkwRjC82FZZ482QnHCBIqNFlAryds/zTa6wdRvFmhQnIM6WxoAl8TM+e9h8MoKkalMc8J/Qfp+WQ7/XdmCg2pp9VvUZTK+g0+G4aGuL+S5+ssZq4rl9k7LrSYyp6vj+djgvISZiz5hPYJCN/WY/gWXfVuLHSpu4CmZt8D2APtT3ax1WmGcuzStAfTW8q3MFIDNV59hkpFmDb+gvyLNbZ95cDYxofiPXaC5cOTftnSBp68Ay1eienqdttEDo4fyakszdvq128KwXkH9azCg6sqLxli6B8l2xdq41MeuJO54VqmOhhLxwKy42NtnJvK/NkNwttH4yMwDPpPbC4vOKCXxT2r2F7jjvJQNB2VFv+oiUAWSSc3fGQcc2uNlx9YQVuzTmjqc7fXAWCGgYoogC8AeNWni204bnBoVpFrEo3gzuOe2fFsddJIclglmTH1hWf31FXUHDO2nl/lT4puQVTo+I+d6jpiV+qdp823NDntRxljRlUJO2AzSTXuIIGtF5q5KWyEi9Nj93BCWa1Llcddkn3ZEZMvDwR4MacwUj8G8hwoH73VvT3jAiakjSpNEIqYCzofeejdfN/gEuuAUfe8uNbTu+gBS+iP3QJe3Pc0Fs/lKJzd3frPNj7xb83wpOf865EQQoOozhnRIKKcMReSjakr/Px5NNooeiJcWEreDagQO2TbwTnHg1kCNG3BAXV/2lV3XBU4afZBoUfxAzYWFOl6xFCAPzhQCPL1SFJp1VRADY/1MU2Kaje5AZoJ4jjph8+yspxBvjic1vC1uYRGW8LWRind9w4eVhCm0LfPiFRCpP+jKPQOJzcNH580/nIMFXPHHnLKv/It7Qex1unDv/QjkuCFFHR6SWJm4WBrwDek+MyOIvgT6o878Cu0Ps472QpoYBQ+7l2WoylWdG1lHZV1UiHPj7PLHPNAL4rbbN3U88fS6N9OJHegQTfcX0i/1KPk4IN/5Z+/15dHI658BINjRvI/6O1QqaTVZkqM8ORcoGpn6BjAiz5rRhjWpOCwlmT+VzOAp3IqACURS1X+txjWE2mfVjlHLJsvyGRDLv1dUR3IeStDAEfsjR/ruRgn5XTFpYaccB/u//DJonJr5A+KFiLbYl+sbbSVAoQCAiAdxKdUpKPx7C473UJ2nYQGby5H5xwboa0Uj0SnJLYWdQ0jvVvzWpWFVWATc4UqnaxdoUDAmewrM6cSSIAmQBB34orCunFbriK9Z4efZ7gB9erQ1fpi3z/IjQBoTEpOUUIPW/qMAApIDPVM6UV9PumW7RL9zKEP5PuWJoGGnKbWGP/b9G4vMFiWMaSNHBYYMI6OLH4WJ3E+4QBGh2vjjfQ0gobhaLgIerIwCQFYEdl9KddAjaflUEFXal9fIQ8Bz9L3rDhQE5AGBZL6ULZmJe3GnkN6Cc+UWAGyD5zv2rsCG2lvR5ox4UE2mFi6nBJbC5Vj5m9Sz1l0QpRwUkH2kD2QQ5iV6nNmQOcU/mz7ulxluf8+FBJJimYVqK8UkJ6+W6j8Eft9Q8fTpEuEVLxqTWGgOAEUBf87RWDU+iF3A+AxFGsJLc5RC+5BKNTEDlV2qDCjHT7b5wqBKJ3FHulOih9EenlZiI51m6kg5yyxnMdbhasvSh6Az8Mp/4lFo/wSA/mXxNhBrEEmRhFiIE5yYUEYIj5F8fH+93tIuWQqyhXIwCntEOdSSmoei9EYFzj8deXcEzVf8y/N6HQErZcJjyg34caOsfRcJYoxEiCm4icA/btWhdjUNT02B20qnxGFndO4CRUQlyDqTbyVD8LRLK9/95L9+5v9zojLle8xQe30dsxKn7r9TTJH8QQai5iam9lU1ik50lwTKpZb18k4rNdO5cnnYoHzCXeCg38YZxyFt9G7um/MxlID5Qd5Ywq6thDzL7WxvanKeRhCuJ2MTVV0EoJxZKIj9Yv0Ars9mZHkoHoP0ikcW8d5ciDj1Onnbj+XDcYI3FZj0Y2vToZvYi/7eLWi8EnSjaIQrr/AHnrmZK1w3Uicd691U6r3Y0UdnzQEl4Ub/l1uhSaGAg2oEdDxkOdZ3Frvf/C4nTEBmunPlNvnJjVFssdeVVXKLBOZ5eRiJjasHUKnTeJVwolvd/dBI+ypfw1+5ae/0upxd9/gV1lbwX9N2yOwqbxz24cKXZWvOFBAGc3+gQFu8RrF6NAeQ96PlkuRsiNOKPPtJT3JNrLGvVKY8g==
+                </xenc:CipherValue>
+            </xenc:CipherData>
+        </xenc:EncryptedData>
+    </saml:EncryptedAssertion>
+</samlp:Response>
\ No newline at end of file
diff --git a/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-signed-logout-request.xml b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-signed-logout-request.xml
new file mode 100644
index 0000000..8c4ab20
--- /dev/null
+++ b/saml-core/src/test/resources/org/keycloak/saml/processing/core/parsers/saml/saml20-signed-logout-request.xml
@@ -0,0 +1,32 @@
+<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns="urn:oasis:names:tc:SAML:2.0:assertion" Destination="http://localhost:8081/auth/realms/saml-demo/protocol/saml" ID="ID_4790c6a3-4b9f-4c0a-a368-5c0e498544e4" IssueInstant="2016-11-01T14:36:43.194Z" Version="2.0">
+    <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://localhost:8080/sales-post-enc/</saml:Issuer>
+    <dsig:Signature xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
+        <dsig:SignedInfo>
+            <dsig:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+            <dsig:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
+            <dsig:Reference URI="#ID_4790c6a3-4b9f-4c0a-a368-5c0e498544e4">
+                <dsig:Transforms>
+                    <dsig:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
+                    <dsig:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+                </dsig:Transforms>
+                <dsig:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
+                <dsig:DigestValue>zeWNo5eav5tFOOCEJ1YU9eINkPnBSfixzAr8AOC4R4c=</dsig:DigestValue>
+            </dsig:Reference>
+        </dsig:SignedInfo>
+        <dsig:SignatureValue>
+            pyOiS1LsV/XR08zhcN6IqSYuKTDln4otmCvZxCc07ORP1C9jragu8V8rEE09qt/zBcdw7Arb8eLNNC6oCnrnMxuvzRInVTwt7T5K3t0UlzRWOb3HMElhcWFEgDzh6uKw5Cr45A01XNpojtJWCML/qU2Enyyy80FBlCJNcbzyLxE=
+        </dsig:SignatureValue>
+        <dsig:KeyInfo>
+            <dsig:KeyValue>
+                <dsig:RSAKeyValue>
+                    <dsig:Modulus>
+                        2+5MCT5BnVN+IYnKZcH6ev1pjXGi4feE0nOycq/VJ3aeaZMi4G9AxOxCBPupErOC7Kgm/Bw5AdJyw+Q12wSRXfJ9FhqCrLXpb7YOhbVSTJ8De5O8mW35DxAlh/cxe9FXjqPb286wKTUZ3LfGYR+X235UQeCTAPS/Ufi21EXaEik=
+                    </dsig:Modulus>
+                    <dsig:Exponent>AQAB</dsig:Exponent>
+                </dsig:RSAKeyValue>
+            </dsig:KeyValue>
+        </dsig:KeyInfo>
+    </dsig:Signature>
+    <saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">bburke</saml:NameID>
+    <samlp:SessionIndex>a3b2df1c-1095-487b-8b56-f62818c449e3</samlp:SessionIndex>
+</samlp:LogoutRequest>
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
index 997bc93..0ef5276 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
@@ -73,9 +73,13 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import java.io.IOException;
-import java.security.PublicKey;
+import java.security.Key;
 import java.security.cert.X509Certificate;
+import java.util.LinkedList;
 import java.util.List;
+import org.keycloak.rotation.HardcodedKeyLocator;
+import org.keycloak.rotation.KeyLocator;
+import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -174,14 +178,20 @@ public class SAMLEndpoint {
         protected abstract void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException;
         protected abstract SAMLDocumentHolder extractRequestDocument(String samlRequest);
         protected abstract SAMLDocumentHolder extractResponseDocument(String response);
-        protected PublicKey getIDPKey() {
-            X509Certificate certificate = null;
-            try {
-                certificate = XMLSignatureUtil.getX509CertificateFromKeyInfoString(config.getSigningCertificate().replaceAll("\\s", ""));
-            } catch (ProcessingException e) {
-                throw new RuntimeException(e);
+        
+        protected KeyLocator getIDPKeyLocator() {
+            List<Key> keys = new LinkedList<>();
+
+            for (String signingCertificate : config.getSigningCertificates()) {
+                try {
+                    X509Certificate cert = XMLSignatureUtil.getX509CertificateFromKeyInfoString(signingCertificate.replaceAll("\\s", ""));
+                    keys.add(cert.getPublicKey());
+                } catch (ProcessingException e) {
+                    throw new RuntimeException(e);
+                }
             }
-            return certificate.getPublicKey();
+
+            return new HardcodedKeyLocator(keys);
         }
 
         public Response execute(String samlRequest, String samlResponse, String relayState) {
@@ -265,14 +275,18 @@ public class SAMLEndpoint {
             builder.issuer(issuerURL);
             JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
                         .relayState(relayState);
+            boolean postBinding = config.isPostBindingResponse();
             if (config.isWantAuthnRequestsSigned()) {
                 KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
-                binding.signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
+                binding.signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
                         .signatureAlgorithm(provider.getSignatureAlgorithm())
                         .signDocument();
+                if (! postBinding && config.isAddExtensionsElementWithKeyInfo()) {    // Only include extension if REDIRECT binding and signing whole SAML protocol message
+                    builder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
+                }
             }
             try {
-                if (config.isPostBindingResponse()) {
+                if (postBinding) {
                     return binding.postBinding(builder.buildDocument()).response(config.getSingleLogoutServiceUrl());
                 } else {
                     return binding.redirectBinding(builder.buildDocument()).response(config.getSingleLogoutServiceUrl());
@@ -418,7 +432,7 @@ public class SAMLEndpoint {
     protected class PostBinding extends Binding {
         @Override
         protected void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException {
-            SamlProtocolUtils.verifyDocumentSignature(documentHolder.getSamlDocument(), getIDPKey());
+            SamlProtocolUtils.verifyDocumentSignature(documentHolder.getSamlDocument(), getIDPKeyLocator());
         }
 
         @Override
@@ -440,8 +454,8 @@ public class SAMLEndpoint {
     protected class RedirectBinding extends Binding {
         @Override
         protected void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException {
-            PublicKey publicKey = getIDPKey();
-            SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo, key);
+            KeyLocator locator = getIDPKeyLocator();
+            SamlProtocolUtils.verifyRedirectSignature(documentHolder, locator, uriInfo, key);
         }
 
 
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
index 104b8f8..f96f15a 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
@@ -50,8 +50,11 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import java.security.KeyPair;
-import java.security.PrivateKey;
-import java.security.PublicKey;
+import java.util.Set;
+import java.util.TreeSet;
+import org.keycloak.dom.saml.v2.metadata.KeyTypes;
+import org.keycloak.keys.KeyMetadata;
+import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
 
 /**
  * @author Pedro Igor
@@ -97,18 +100,22 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
                     .nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat));
             JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
                     .relayState(request.getState());
+            boolean postBinding = getConfig().isPostBindingAuthnRequest();
 
             if (getConfig().isWantAuthnRequestsSigned()) {
                 KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
 
                 KeyPair keypair = new KeyPair(keys.getPublicKey(), keys.getPrivateKey());
 
-                binding.signWith(keypair);
+                binding.signWith(keys.getKid(), keypair);
                 binding.signatureAlgorithm(getSignatureAlgorithm());
                 binding.signDocument();
+                if (! postBinding && getConfig().isAddExtensionsElementWithKeyInfo()) {    // Only include extension if REDIRECT binding and signing whole SAML protocol message
+                    authnRequestBuilder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
+                }
             }
 
-            if (getConfig().isPostBindingAuthnRequest()) {
+            if (postBinding) {
                 return binding.postBinding(authnRequestBuilder.toDocument()).request(destinationUrl);
             } else {
                 return binding.redirectBinding(authnRequestBuilder.toDocument()).request(destinationUrl);
@@ -198,7 +205,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
                 .relayState(userSession.getId());
         if (getConfig().isWantAuthnRequestsSigned()) {
             KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
-            binding.signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
+            binding.signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
                     .signatureAlgorithm(getSignatureAlgorithm())
                     .signDocument();
         }
@@ -225,11 +232,27 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
         boolean wantAuthnRequestsSigned = getConfig().isWantAuthnRequestsSigned();
         String entityId = getEntityId(uriInfo, realm);
         String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
-        String certificatePem = PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate());
-        String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint, wantAuthnRequestsSigned, entityId, nameIDPolicyFormat, certificatePem);
+
+        StringBuilder keysString = new StringBuilder();
+        Set<KeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
+          ? (int) (o2.getProviderPriority() - o1.getProviderPriority())
+          : (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1));
+        keys.addAll(session.keys().getKeys(realm, false));
+        for (KeyMetadata key : keys) {
+            addKeyInfo(keysString, key, KeyTypes.SIGNING.value());
+        }
+        String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint, wantAuthnRequestsSigned, entityId, nameIDPolicyFormat, keysString.toString());
         return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
     }
 
+    private static void addKeyInfo(StringBuilder target, KeyMetadata key, String purpose) {
+        if (key == null) {
+            return;
+        }
+
+        target.append(SPMetadataDescriptor.xmlKeyInfo("        ", key.getKid(), PemUtils.encodeCertificate(key.getCertificate()), purpose, true));
+    }
+
     public SignatureAlgorithm getSignatureAlgorithm() {
         String alg = getConfig().getSignatureAlgorithm();
         if (alg != null) {
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
index 1b2fb67..59b46ca 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
@@ -62,14 +62,45 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
         getConfig().put("forceAuthn", String.valueOf(forceAuthn));
     }
 
+    /**
+     * @deprecated Prefer {@link #getSigningCertificates()}}
+     * @param signingCertificate
+     */
     public String getSigningCertificate() {
-        return getConfig().get("signingCertificate");
+        return getConfig().get(SIGNING_CERTIFICATE_KEY);
     }
 
+    /**
+     * @deprecated Prefer {@link #addSigningCertificate(String)}}
+     * @param signingCertificate
+     */
     public void setSigningCertificate(String signingCertificate) {
-        getConfig().put("signingCertificate", signingCertificate);
+        getConfig().put(SIGNING_CERTIFICATE_KEY, signingCertificate);
     }
 
+    public void addSigningCertificate(String signingCertificate) {
+        String crt = getConfig().get(SIGNING_CERTIFICATE_KEY);
+        if (crt == null || crt.isEmpty()) {
+            getConfig().put(SIGNING_CERTIFICATE_KEY, signingCertificate);
+        } else {
+            // Note that "," is not coding character per PEM format specification:
+            // see https://tools.ietf.org/html/rfc1421, section 4.3.2.4 Step 4: Printable Encoding
+            getConfig().put(SIGNING_CERTIFICATE_KEY, crt + "," + signingCertificate);
+        }
+    }
+
+    public String[] getSigningCertificates() {
+        String crt = getConfig().get(SIGNING_CERTIFICATE_KEY);
+        if (crt == null || crt.isEmpty()) {
+            return new String[] { };
+        }
+        // Note that "," is not coding character per PEM format specification:
+        // see https://tools.ietf.org/html/rfc1421, section 4.3.2.4 Step 4: Printable Encoding
+        return crt.split(",");
+    }
+
+    public static final String SIGNING_CERTIFICATE_KEY = "signingCertificate";
+
     public String getNameIDPolicyFormat() {
         return getConfig().get("nameIDPolicyFormat");
     }
@@ -86,6 +117,14 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
         getConfig().put("wantAuthnRequestsSigned", String.valueOf(wantAuthnRequestsSigned));
     }
 
+    public boolean isAddExtensionsElementWithKeyInfo() {
+        return Boolean.valueOf(getConfig().get("addExtensionsElementWithKeyInfo"));
+    }
+
+    public void setAddExtensionsElementWithKeyInfo(boolean addExtensionsElementWithKeyInfo) {
+        getConfig().put("addExtensionsElementWithKeyInfo", String.valueOf(addExtensionsElementWithKeyInfo));
+    }
+
     public String getSignatureAlgorithm() {
         return getConfig().get("signatureAlgorithm");
     }
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
index 714c47e..0cc72da 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
@@ -108,6 +108,7 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
                     samlIdentityProviderConfig.setSingleLogoutServiceUrl(singleLogoutServiceUrl);
                     samlIdentityProviderConfig.setSingleSignOnServiceUrl(singleSignOnServiceUrl);
                     samlIdentityProviderConfig.setWantAuthnRequestsSigned(idpDescriptor.isWantAuthnRequestsSigned());
+                    samlIdentityProviderConfig.setAddExtensionsElementWithKeyInfo(false);
                     samlIdentityProviderConfig.setValidateSignature(idpDescriptor.isWantAuthnRequestsSigned());
                     samlIdentityProviderConfig.setPostBindingResponse(postBinding);
                     samlIdentityProviderConfig.setPostBindingAuthnRequest(postBinding);
@@ -121,7 +122,7 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
                             Element x509KeyInfo = DocumentUtil.getChildElement(keyInfo, new QName("dsig", "X509Certificate"));
 
                             if (KeyTypes.SIGNING.equals(keyDescriptorType.getUse())) {
-                                samlIdentityProviderConfig.setSigningCertificate(x509KeyInfo.getTextContent());
+                                samlIdentityProviderConfig.addSigningCertificate(x509KeyInfo.getTextContent());
                             } else if (KeyTypes.ENCRYPTION.equals(keyDescriptorType.getUse())) {
                                 samlIdentityProviderConfig.setEncryptionPublicKey(x509KeyInfo.getTextContent());
                             } else if (keyDescriptorType.getUse() ==  null) {
@@ -131,8 +132,8 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
                     }
 
                     if (defaultCertificate != null) {
-                        if (samlIdentityProviderConfig.getSigningCertificate() == null) {
-                            samlIdentityProviderConfig.setSigningCertificate(defaultCertificate);
+                        if (samlIdentityProviderConfig.getSigningCertificates().length == 0) {
+                            samlIdentityProviderConfig.addSigningCertificate(defaultCertificate);
                         }
 
                         if (samlIdentityProviderConfig.getEncryptionPublicKey() == null) {
diff --git a/services/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java b/services/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java
index 00caa11..cca12cb 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java
@@ -101,6 +101,7 @@ public class EntityDescriptorDescriptionConverter implements ClientDescriptionCo
         app.setFullScopeAllowed(true);
         app.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
         attributes.put(SamlConfigAttributes.SAML_SERVER_SIGNATURE, SamlProtocol.ATTRIBUTE_TRUE_VALUE); // default to true
+        attributes.put(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_EXT, SamlProtocol.ATTRIBUTE_FALSE_VALUE); // default to false
         attributes.put(SamlConfigAttributes.SAML_SIGNATURE_ALGORITHM, SignatureAlgorithm.RSA_SHA256.toString());
         attributes.put(SamlConfigAttributes.SAML_AUTHNSTATEMENT, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
         SPSSODescriptorType spDescriptorType = CoreConfigUtil.getSPDescriptor(entity);
diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java
index 2175b32..14166ce 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java
@@ -18,7 +18,6 @@
 package org.keycloak.protocol.saml.installation;
 
 import org.keycloak.Config;
-import org.keycloak.common.util.PemUtils;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
@@ -42,14 +41,14 @@ public class KeycloakSamlClientInstallation implements ClientInstallationProvide
     @Override
     public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri) {
         SamlClient samlClient = new SamlClient(client);
-        StringBuffer buffer = new StringBuffer();
+        StringBuilder buffer = new StringBuilder();
         buffer.append("<keycloak-saml-adapter>\n");
         baseXml(session, realm, client, baseUri, samlClient, buffer);
         buffer.append("</keycloak-saml-adapter>\n");
         return Response.ok(buffer.toString(), MediaType.TEXT_PLAIN_TYPE).build();
     }
 
-    public static void baseXml(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri, SamlClient samlClient, StringBuffer buffer) {
+    public static void baseXml(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri, SamlClient samlClient, StringBuilder buffer) {
         buffer.append("    <SP entityID=\"").append(client.getClientId()).append("\"\n");
         buffer.append("        sslPolicy=\"").append(realm.getSslRequired().name()).append("\"\n");
         buffer.append("        logoutPage=\"SPECIFY YOUR LOGOUT PAGE!\">\n");
@@ -113,15 +112,6 @@ public class KeycloakSamlClientInstallation implements ClientInstallationProvide
         buffer.append("                                 postBindingUrl=\"").append(bindingUrl).append("\"\n");
         buffer.append("                                 redirectBindingUrl=\"").append(bindingUrl).append("\"");
         buffer.append("/>\n");
-        if (samlClient.requiresRealmSignature()) {
-            buffer.append("            <Keys>\n");
-            buffer.append("                <Key signing=\"true\">\n");
-            buffer.append("                    <CertificatePem>\n");
-            buffer.append("                       ").append(PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate())).append("\n");
-            buffer.append("                    </CertificatePem>\n");
-            buffer.append("                </Key>\n");
-            buffer.append("            </Keys>\n");
-        }
         buffer.append("        </IDP>\n");
         buffer.append("    </SP>\n");
     }
@@ -138,7 +128,7 @@ public class KeycloakSamlClientInstallation implements ClientInstallationProvide
 
     @Override
     public String getHelpText() {
-        return "Keycloak SAML adapter configuration file.  Put this in WEB-INF directory if your WAR.";
+        return "Keycloak SAML adapter configuration file.  Put this in WEB-INF directory of your WAR.";
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlSubsystemInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlSubsystemInstallation.java
index ea77d47..bde0ccd 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlSubsystemInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlSubsystemInstallation.java
@@ -39,7 +39,7 @@ public class KeycloakSamlSubsystemInstallation implements ClientInstallationProv
     @Override
     public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri) {
         SamlClient samlClient = new SamlClient(client);
-        StringBuffer buffer = new StringBuffer();
+        StringBuilder buffer = new StringBuilder();
         buffer.append("<secure-deployment name=\"YOUR-WAR.war\">\n");
         KeycloakSamlClientInstallation.baseXml(session, realm, client, baseUri, samlClient, buffer);
         buffer.append("</secure-deployment>\n");
diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java
index 4b84363..3c451b3 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java
@@ -32,6 +32,11 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import java.net.URI;
+import java.util.Set;
+import java.util.TreeSet;
+import org.keycloak.dom.saml.v2.metadata.KeyTypes;
+import org.keycloak.keys.KeyMetadata;
+import org.keycloak.saml.SPMetadataDescriptor;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -41,49 +46,61 @@ public class SamlIDPDescriptorClientInstallation implements ClientInstallationPr
     public static String getIDPDescriptorForClient(KeycloakSession session, RealmModel realm, ClientModel client, URI serverBaseUri) {
         SamlClient samlClient = new SamlClient(client);
         String idpEntityId = RealmsResource.realmBaseUrl(UriBuilder.fromUri(serverBaseUri)).build(realm.getName()).toString();
-        String idp = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
-                 "<EntityDescriptor entityID=\"" + idpEntityId + "\"\n" +
-                "                   xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\"\n" +
-                "                   xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n" +
-                "   <IDPSSODescriptor WantAuthnRequestsSigned=\"" + Boolean.toString(samlClient.requiresClientSignature()) + "\"\n" +
-                "      protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n";
+        StringBuilder sb = new StringBuilder();
+        sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+          + "<EntityDescriptor entityID=\"").append(idpEntityId).append("\"\n"
+          + "                   xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\"\n"
+          + "                   xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\"\n"
+          + "                   xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"
+          + "   <IDPSSODescriptor WantAuthnRequestsSigned=\"")
+          .append(samlClient.requiresClientSignature())
+          .append("\"\n"
+            + "      protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n");
         if (samlClient.forceNameIDFormat() && samlClient.getNameIDFormat() != null) {
-            idp +=  "   <NameIDFormat>" + samlClient.getNameIDFormat() + "</NameIDFormat>\n";
+            sb.append("   <NameIDFormat>").append(samlClient.getNameIDFormat()).append("</NameIDFormat>\n");
         } else {
-            idp +=  "   <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>\n" +
-                    "   <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n" +
-                    "   <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>\n" +
-                    "   <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>\n";
+            sb.append("   <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>\n"
+              + "   <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n"
+              + "   <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>\n"
+              + "   <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>\n");
         }
         String bindUrl = RealmsResource.protocolUrl(UriBuilder.fromUri(serverBaseUri)).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString();
-        idp +=  "\n" +
-                "      <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n" +
-                "         Location=\"" + bindUrl + "\" />\n";
-        if (!samlClient.forcePostBinding()) {
-           idp +=   "      <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"\n" +
-                    "         Location=\"" + bindUrl + "\" />\n";
+        sb.append("\n"
+          + "      <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n"
+          + "         Location=\"").append(bindUrl).append("\" />\n");
+        if (! samlClient.forcePostBinding()) {
+           sb.append("      <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"\n"
+             + "         Location=\"").append(bindUrl).append("\" />\n");
 
         }
-        idp +=  "      <SingleLogoutService\n" +
-                "         Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n" +
-                "         Location=\"" + bindUrl + "\" />\n";
-        if (!samlClient.forcePostBinding()) {
-            idp +=  "      <SingleLogoutService\n" +
-                    "         Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"\n" +
-                    "         Location=\"" + bindUrl + "\" />\n";
+        sb.append("      <SingleLogoutService\n"
+          + "         Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n"
+          + "         Location=\"").append(bindUrl).append("\" />\n");
+        if (! samlClient.forcePostBinding()) {
+            sb.append("      <SingleLogoutService\n"
+              + "         Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"\n"
+              + "         Location=\"").append(bindUrl).append("\" />\n");
         }
-        idp +=  "      <KeyDescriptor use=\"signing\">\n" +
-                "          <dsig:KeyInfo xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">\n" +
-                "              <dsig:X509Data>\n" +
-                "                  <dsig:X509Certificate>\n" +
-                "                      " + PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate()) + "\n" +
-                "                  </dsig:X509Certificate>\n" +
-                "              </dsig:X509Data>\n" +
-                "          </dsig:KeyInfo>\n" +
-                "      </KeyDescriptor>\n" +
-                "   </IDPSSODescriptor>\n" +
-                "</EntityDescriptor>\n";
-        return idp;
+
+        Set<KeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
+          ? (int) (o2.getProviderPriority() - o1.getProviderPriority())
+          : (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1));
+        keys.addAll(session.keys().getKeys(realm, false));
+        for (KeyMetadata key : keys) {
+            addKeyInfo(sb, key, KeyTypes.SIGNING.value());
+        }
+
+        sb.append("   </IDPSSODescriptor>\n"
+          + "</EntityDescriptor>\n");
+        return sb.toString();
+    }
+
+    private static void addKeyInfo(StringBuilder target, KeyMetadata key, String purpose) {
+        if (key == null) {
+            return;
+        }
+
+        target.append(SPMetadataDescriptor.xmlKeyInfo("      ", key.getKid(), PemUtils.encodeCertificate(key.getCertificate()), purpose, false));
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java
index 9d12242..6349953 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java
@@ -31,6 +31,7 @@ import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import java.net.URI;
+import org.keycloak.dom.saml.v2.metadata.KeyTypes;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -45,7 +46,8 @@ public class SamlSPDescriptorClientInstallation implements ClientInstallationPro
         if (logoutUrl == null) logoutUrl = client.getManagementUrl();
         String nameIdFormat = samlClient.getNameIDFormat();
         if (nameIdFormat == null) nameIdFormat = SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT;
-        return SPMetadataDescriptor.getSPDescriptor(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get(), assertionUrl, logoutUrl, samlClient.requiresClientSignature(), client.getClientId(), nameIdFormat, samlClient.getClientSigningCertificate());
+        String spCertificate = SPMetadataDescriptor.xmlKeyInfo("        ", null, samlClient.getClientSigningCertificate(), KeyTypes.SIGNING.value(), true);
+        return SPMetadataDescriptor.getSPDescriptor(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get(), assertionUrl, logoutUrl, samlClient.requiresClientSignature(), client.getClientId(), nameIdFormat, spCertificate);
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlClient.java b/services/src/main/java/org/keycloak/protocol/saml/SamlClient.java
index 0415a72..ee5aaba 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlClient.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlClient.java
@@ -23,6 +23,8 @@ import org.keycloak.saml.SignatureAlgorithm;
 import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
 
 /**
+ * Configuration of a SAML-enabled client.
+ *
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
@@ -116,7 +118,14 @@ public class SamlClient extends ClientConfigResolver {
 
     public void setRequiresRealmSignature(boolean val) {
         client.setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE, Boolean.toString(val));
+    }
+
+    public boolean addExtensionsElementWithKeyInfo() {
+        return "true".equals(resolveAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_EXT));
+    }
 
+    public void setAddExtensionsElementWithKeyInfo(boolean val) {
+        client.setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_EXT, Boolean.toString(val));
     }
 
     public boolean forcePostBinding() {
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlClientTemplate.java b/services/src/main/java/org/keycloak/protocol/saml/SamlClientTemplate.java
index e5bc2fa..0af3be0 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlClientTemplate.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlClientTemplate.java
@@ -17,6 +17,7 @@
 
 package org.keycloak.protocol.saml;
 
+import java.util.Objects;
 import org.keycloak.models.ClientTemplateModel;
 import org.keycloak.saml.SignatureAlgorithm;
 
@@ -89,7 +90,14 @@ public class SamlClientTemplate {
 
     public void setRequiresRealmSignature(boolean val) {
         clientTemplate.setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE, Boolean.toString(val));
+    }
+
+    public boolean addExtensionsElementWithKeyInfo() {
+        return Objects.equals("true", clientTemplate.getAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_EXT));
+    }
 
+    public void setAddExtensionsElementWithKeyInfo(boolean val) {
+        clientTemplate.setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_EXT, Boolean.toString(val));
     }
 
     public boolean forcePostBinding() {
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlConfigAttributes.java b/services/src/main/java/org/keycloak/protocol/saml/SamlConfigAttributes.java
index 3356c31..9837179 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlConfigAttributes.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlConfigAttributes.java
@@ -31,6 +31,7 @@ public interface SamlConfigAttributes {
     String SAML_AUTHNSTATEMENT = "saml.authnstatement";
     String SAML_FORCE_NAME_ID_FORMAT_ATTRIBUTE = "saml_force_name_id_format";
     String SAML_SERVER_SIGNATURE = "saml.server.signature";
+    String SAML_SERVER_SIGNATURE_KEYINFO_EXT = "saml.server.signature.keyinfo.ext";
     String SAML_FORCE_POST_BINDING = "saml.force.post.binding";
     String SAML_ASSERTION_SIGNATURE = "saml.assertion.signature";
     String SAML_ENCRYPT = "saml.encrypt";
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
index 14726d3..486633f 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -74,8 +74,10 @@ import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
+import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -98,6 +100,7 @@ public class SamlProtocol implements LoginProtocol {
     public static final String SAML_REDIRECT_BINDING = "get";
     public static final String SAML_REQUEST_ID = "SAML_REQUEST_ID";
     public static final String SAML_LOGOUT_BINDING = "saml.logout.binding";
+    public static final String SAML_LOGOUT_ADD_EXTENSIONS_ELEMENT_WITH_KEY_INFO = "saml.logout.addExtensionsElementWithKeyInfo";
     public static final String SAML_LOGOUT_REQUEST_ID = "SAML_LOGOUT_REQUEST_ID";
     public static final String SAML_LOGOUT_RELAY_STATE = "SAML_LOGOUT_RELAY_STATE";
     public static final String SAML_LOGOUT_CANONICALIZATION = "SAML_LOGOUT_CANONICALIZATION";
@@ -373,7 +376,15 @@ public class SamlProtocol implements LoginProtocol {
         }
 
         Document samlDocument = null;
+        KeyManager keyManager = session.keys();
+        KeyManager.ActiveKey keys = keyManager.getActiveKey(realm);
+        boolean postBinding = isPostBinding(clientSession);
+
         try {
+            if ((! postBinding) && samlClient.requiresRealmSignature() && samlClient.addExtensionsElementWithKeyInfo()) {
+                builder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
+            }
+
             ResponseType samlModel = builder.buildModel();
             final AttributeStatementType attributeStatement = populateAttributeStatements(attributeStatementMappers, session, userSession, clientSession);
             populateRoles(roleListMapper, session, userSession, clientSession, attributeStatement);
@@ -394,22 +405,19 @@ public class SamlProtocol implements LoginProtocol {
         JaxrsSAML2BindingBuilder bindingBuilder = new JaxrsSAML2BindingBuilder();
         bindingBuilder.relayState(relayState);
 
-        KeyManager keyManager = session.keys();
-        KeyManager.ActiveKey keys = keyManager.getActiveKey(realm);
-
         if (samlClient.requiresRealmSignature()) {
             String canonicalization = samlClient.getCanonicalizationMethod();
             if (canonicalization != null) {
                 bindingBuilder.canonicalizationMethod(canonicalization);
             }
-            bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
+            bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
         }
         if (samlClient.requiresAssertionSignature()) {
             String canonicalization = samlClient.getCanonicalizationMethod();
             if (canonicalization != null) {
                 bindingBuilder.canonicalizationMethod(canonicalization);
             }
-            bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signAssertions();
+            bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signAssertions();
         }
         if (samlClient.requiresEncryption()) {
             PublicKey publicKey = null;
@@ -496,12 +504,17 @@ public class SamlProtocol implements LoginProtocol {
             if (isLogoutPostBindingForClient(clientSession)) {
                 String bindingUri = getLogoutServiceUrl(uriInfo, client, SAML_POST_BINDING);
                 SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(bindingUri, clientSession, client);
+                // This is POST binding, hence KeyID is included in dsig:KeyInfo/dsig:KeyName, no need to add <samlp:Extensions> element
                 JaxrsSAML2BindingBuilder binding = createBindingBuilder(samlClient);
                 return binding.postBinding(logoutBuilder.buildDocument()).request(bindingUri);
             } else {
                 logger.debug("frontchannel redirect binding");
                 String bindingUri = getLogoutServiceUrl(uriInfo, client, SAML_REDIRECT_BINDING);
                 SAML2LogoutRequestBuilder logoutBuilder = createLogoutRequest(bindingUri, clientSession, client);
+                if (samlClient.requiresRealmSignature() && samlClient.addExtensionsElementWithKeyInfo()) {
+                    KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
+                    logoutBuilder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
+                }
                 JaxrsSAML2BindingBuilder binding = createBindingBuilder(samlClient);
                 return binding.redirectBinding(logoutBuilder.buildDocument()).request(bindingUri);
             }
@@ -534,6 +547,7 @@ public class SamlProtocol implements LoginProtocol {
         JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder();
         binding.relayState(logoutRelayState);
         String signingAlgorithm = userSession.getNote(SAML_LOGOUT_SIGNATURE_ALGORITHM);
+        boolean postBinding = isLogoutPostBindingForInitiator(userSession);
         if (signingAlgorithm != null) {
             SignatureAlgorithm algorithm = SignatureAlgorithm.valueOf(signingAlgorithm);
             String canonicalization = userSession.getNote(SAML_LOGOUT_CANONICALIZATION);
@@ -541,7 +555,11 @@ public class SamlProtocol implements LoginProtocol {
                 binding.canonicalizationMethod(canonicalization);
             }
             KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
-            binding.signatureAlgorithm(algorithm).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
+            binding.signatureAlgorithm(algorithm).signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
+            boolean addExtension = (! postBinding) && Objects.equals("true", userSession.getNote(SamlProtocol.SAML_LOGOUT_ADD_EXTENSIONS_ELEMENT_WITH_KEY_INFO));
+            if (addExtension) {    // Only include extension if REDIRECT binding and signing whole SAML protocol message
+                builder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
+            }
         }
 
         try {
@@ -577,6 +595,7 @@ public class SamlProtocol implements LoginProtocol {
         String logoutRequestString = null;
         try {
             JaxrsSAML2BindingBuilder binding = createBindingBuilder(samlClient);
+            // This is POST binding, hence KeyID is included in dsig:KeyInfo/dsig:KeyName, no need to add <samlp:Extensions> element
             logoutRequestString = binding.postBinding(logoutBuilder.buildDocument()).encoded();
         } catch (Exception e) {
             logger.warn("failed to send saml logout", e);
@@ -639,7 +658,7 @@ public class SamlProtocol implements LoginProtocol {
         JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder();
         if (samlClient.requiresRealmSignature()) {
             KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
-            binding.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
+            binding.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
         }
         return binding;
     }
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
index e1a7c98..026a54a 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
@@ -17,6 +17,7 @@
 
 package org.keycloak.protocol.saml;
 
+import java.security.Key;
 import org.keycloak.common.VerificationException;
 import org.keycloak.common.util.PemUtils;
 import org.keycloak.models.ClientModel;
@@ -33,6 +34,15 @@ import javax.ws.rs.core.UriInfo;
 import java.security.PublicKey;
 import java.security.Signature;
 import java.security.cert.Certificate;
+import org.keycloak.dom.saml.v2.SAML2Object;
+import org.keycloak.dom.saml.v2.protocol.ExtensionsType;
+import org.keycloak.dom.saml.v2.protocol.RequestAbstractType;
+import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
+import org.keycloak.rotation.HardcodedKeyLocator;
+import org.keycloak.rotation.KeyLocator;
+import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
+import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
+import org.w3c.dom.Element;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -40,20 +50,36 @@ import java.security.cert.Certificate;
  */
 public class SamlProtocolUtils {
 
-
+    /**
+     * Verifies a signature of the given SAML document using settings for the given client.
+     * Throws an exception if the client signature is expected to be present as per the client
+     * settings and it is invalid, otherwise returns back to the caller.
+     *
+     * @param client
+     * @param document
+     * @throws VerificationException
+     */
     public static void verifyDocumentSignature(ClientModel client, Document document) throws VerificationException {
         SamlClient samlClient = new SamlClient(client);
         if (!samlClient.requiresClientSignature()) {
             return;
         }
         PublicKey publicKey = getSignatureValidationKey(client);
-        verifyDocumentSignature(document, publicKey);
+        verifyDocumentSignature(document, new HardcodedKeyLocator(publicKey));
     }
 
-    public static void verifyDocumentSignature(Document document, PublicKey publicKey) throws VerificationException {
+    /**
+     * Verifies a signature of the given SAML document using keys obtained from the given key locator.
+     * Throws an exception if the client signature is invalid, otherwise returns back to the caller.
+     *
+     * @param document
+     * @param keyLocator
+     * @throws VerificationException
+     */
+    public static void verifyDocumentSignature(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) {
@@ -61,10 +87,22 @@ public class SamlProtocolUtils {
         }
     }
 
+    /**
+     * Returns public part of SAML signing key from the client settings.
+     * @param client
+     * @return Public key for signature validation.
+     * @throws VerificationException
+     */
     public static PublicKey getSignatureValidationKey(ClientModel client) throws VerificationException {
         return getPublicKey(new SamlClient(client).getClientSigningCertificate());
     }
 
+    /**
+     * Returns public part of SAML encryption key from the client settings.
+     * @param client
+     * @return Public key for encryption.
+     * @throws VerificationException
+     */
     public static PublicKey getEncryptionValidationKey(ClientModel client) throws VerificationException {
         return getPublicKey(client, SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE);
     }
@@ -85,7 +123,7 @@ public class SamlProtocolUtils {
         return cert.getPublicKey();
     }
 
-    public static void verifyRedirectSignature(PublicKey publicKey, UriInfo uriInformation, String paramKey) throws VerificationException {
+    public static void verifyRedirectSignature(SAMLDocumentHolder documentHolder, KeyLocator locator, UriInfo uriInformation, String paramKey) throws VerificationException {
         MultivaluedMap<String, String> encodedParams = uriInformation.getQueryParameters(false);
         String request = encodedParams.getFirst(paramKey);
         String algorithm = encodedParams.getFirst(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
@@ -96,10 +134,11 @@ public class SamlProtocolUtils {
         if (algorithm == null) throw new VerificationException("SigAlg was null");
         if (signature == null) throw new VerificationException("Signature was null");
 
+        String keyId = getMessageSigningKeyId(documentHolder.getSamlObject());
+
         // Shibboleth doesn't sign the document for redirect binding.
         // todo maybe a flag?
 
-
         UriBuilder builder = UriBuilder.fromPath("/")
                 .queryParam(paramKey, request);
         if (encodedParams.containsKey(GeneralConstants.RELAY_STATE)) {
@@ -113,8 +152,13 @@ public class SamlProtocolUtils {
 
             SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.getFromXmlMethod(decodedAlgorithm);
             Signature validator = signatureAlgorithm.createSignature(); // todo plugin signature alg
-            validator.initVerify(publicKey);
-            validator.update(rawQuery.getBytes("UTF-8"));
+            Key key = locator.getKey(keyId);
+            if (key instanceof PublicKey) {
+                validator.initVerify((PublicKey) key);
+                validator.update(rawQuery.getBytes("UTF-8"));
+            } else {
+                throw new VerificationException("Invalid key locator for signature verification");
+            }
             if (!validator.verify(decodedSignature)) {
                 throw new VerificationException("Invalid query param signature");
             }
@@ -123,5 +167,32 @@ public class SamlProtocolUtils {
         }
     }
 
+    private static 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;
+    }
 }
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlRepresentationAttributes.java b/services/src/main/java/org/keycloak/protocol/saml/SamlRepresentationAttributes.java
index b2b4ee4..a67374a 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlRepresentationAttributes.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlRepresentationAttributes.java
@@ -64,7 +64,11 @@ public class SamlRepresentationAttributes {
     public String getSamlServerSignature() {
         if (getAttributes() == null) return null;
         return getAttributes().get(SamlConfigAttributes.SAML_SERVER_SIGNATURE);
+    }
 
+    public String getAddExtensionsElementWithKeyInfo() {
+        if (getAttributes() == null) return null;
+        return getAttributes().get(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_EXT);
     }
 
     public String getForcePostBinding() {
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
index ea01085..14c5503 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -74,6 +74,17 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
 import java.security.PublicKey;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import org.keycloak.common.util.StringPropertyReplacer;
+import org.keycloak.dom.saml.v2.metadata.KeyTypes;
+import org.keycloak.keys.KeyMetadata;
+import org.keycloak.rotation.HardcodedKeyLocator;
+import org.keycloak.rotation.KeyLocator;
+import org.keycloak.saml.SPMetadataDescriptor;
+import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
 
 /**
  * Resource class for the oauth/openid connect token service
@@ -336,6 +347,8 @@ public class SamlService extends AuthorizationEndpointBase {
                 String logoutBinding = getBindingType();
                 if ("true".equals(samlClient.forcePostBinding()))
                     logoutBinding = SamlProtocol.SAML_POST_BINDING;
+                boolean postBinding = Objects.equals(SamlProtocol.SAML_POST_BINDING, logoutBinding);
+
                 String bindingUri = SamlProtocol.getLogoutServiceUrl(uriInfo, client, logoutBinding);
                 UserSessionModel userSession = authResult.getSession();
                 userSession.setNote(SamlProtocol.SAML_LOGOUT_BINDING_URI, bindingUri);
@@ -347,6 +360,7 @@ public class SamlService extends AuthorizationEndpointBase {
                     userSession.setNote(SamlProtocol.SAML_LOGOUT_RELAY_STATE, relayState);
                 userSession.setNote(SamlProtocol.SAML_LOGOUT_REQUEST_ID, logoutRequest.getID());
                 userSession.setNote(SamlProtocol.SAML_LOGOUT_BINDING, logoutBinding);
+                userSession.setNote(SamlProtocol.SAML_LOGOUT_ADD_EXTENSIONS_ELEMENT_WITH_KEY_INFO, Boolean.toString((! postBinding) && samlClient.addExtensionsElementWithKeyInfo()));
                 userSession.setNote(SamlProtocol.SAML_LOGOUT_CANONICALIZATION, samlClient.getCanonicalizationMethod());
                 userSession.setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, SamlProtocol.LOGIN_PROTOCOL);
                 // remove client from logout requests
@@ -397,14 +411,17 @@ public class SamlService extends AuthorizationEndpointBase {
             builder.destination(logoutBindingUri);
             builder.issuer(RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString());
             JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(logoutRelayState);
+            boolean postBinding = SamlProtocol.SAML_POST_BINDING.equals(logoutBinding);
             if (samlClient.requiresRealmSignature()) {
                 SignatureAlgorithm algorithm = samlClient.getSignatureAlgorithm();
                 KeyManager.ActiveKey keys = session.keys().getActiveKey(realm);
-                binding.signatureAlgorithm(algorithm).signWith(keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
-
+                binding.signatureAlgorithm(algorithm).signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate()).signDocument();
+                if (! postBinding && samlClient.addExtensionsElementWithKeyInfo()) {    // Only include extension if REDIRECT binding and signing whole SAML protocol message
+                    builder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
+                }
             }
             try {
-                if (SamlProtocol.SAML_POST_BINDING.equals(logoutBinding)) {
+                if (postBinding) {
                     return binding.postBinding(builder.buildDocument()).response(logoutBindingUri);
                 } else {
                     return binding.redirectBinding(builder.buildDocument()).response(logoutBindingUri);
@@ -466,7 +483,8 @@ public class SamlService extends AuthorizationEndpointBase {
                 return;
             }
             PublicKey publicKey = SamlProtocolUtils.getSignatureValidationKey(client);
-            SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo, GeneralConstants.SAML_REQUEST_KEY);
+            KeyLocator clientKeyLocator = new HardcodedKeyLocator(publicKey);
+            SamlProtocolUtils.verifyRedirectSignature(documentHolder, clientKeyLocator, uriInfo, GeneralConstants.SAML_REQUEST_KEY);
         }
 
         @Override
@@ -541,12 +559,30 @@ public class SamlService extends AuthorizationEndpointBase {
     public static String getIDPMetadataDescriptor(UriInfo uriInfo, KeycloakSession session, RealmModel realm) throws IOException {
         InputStream is = SamlService.class.getResourceAsStream("/idp-metadata-template.xml");
         String template = StreamUtil.readString(is);
-        template = template.replace("${idp.entityID}", RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString());
-        template = template.replace("${idp.sso.HTTP-POST}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
-        template = template.replace("${idp.sso.HTTP-Redirect}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
-        template = template.replace("${idp.sls.HTTP-POST}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
-        template = template.replace("${idp.signing.certificate}", PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate()));
-        return template;
+        Properties props = new Properties();
+        props.put("idp.entityID", RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString());
+        props.put("idp.sso.HTTP-POST", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
+        props.put("idp.sso.HTTP-Redirect", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
+        props.put("idp.sls.HTTP-POST", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
+        StringBuilder keysString = new StringBuilder();
+        Set<KeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
+          ? (int) (o2.getProviderPriority() - o1.getProviderPriority())
+          : (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1));
+        keys.addAll(session.keys().getKeys(realm, false));
+        for (KeyMetadata key : keys) {
+            addKeyInfo(keysString, key, KeyTypes.SIGNING.value());
+        }
+        props.put("idp.signing.certificates", keysString.toString());
+        return StringPropertyReplacer.replaceProperties(template, props);
+    }
+
+    private static void addKeyInfo(StringBuilder target, KeyMetadata key, String purpose) {
+        if (key == null) {
+            return;
+        }
+
+        target.append(SPMetadataDescriptor.xmlKeyInfo("                        ",
+          key.getKid(), PemUtils.encodeCertificate(key.getCertificate()), purpose, false));
     }
 
     @GET
diff --git a/services/src/main/resources/idp-metadata-template.xml b/services/src/main/resources/idp-metadata-template.xml
index 0a53647..a4416cd 100755
--- a/services/src/main/resources/idp-metadata-template.xml
+++ b/services/src/main/resources/idp-metadata-template.xml
@@ -16,22 +16,12 @@
   ~ limitations under the License.
   -->
 
-<EntitiesDescriptor Name="urn:keycloak"
-					xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+<EntitiesDescriptor Name="urn:keycloak" xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
 					xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
 	<EntityDescriptor entityID="${idp.entityID}">
 		<IDPSSODescriptor WantAuthnRequestsSigned="true"
 			protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
-
-			<KeyDescriptor use="signing">
-				 <dsig:KeyInfo>
-					 <dsig:X509Data>
-						 <dsig:X509Certificate>
-							 ${idp.signing.certificate}
-						 </dsig:X509Certificate>
-					 </dsig:X509Data>
-				 </dsig:KeyInfo>
-			</KeyDescriptor>
+${idp.signing.certificates}
 			<SingleLogoutService
 					Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
 					Location="${idp.sls.HTTP-POST}" />
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
index dbf30e9..09ddb39 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
@@ -193,6 +193,7 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
         assertEquals(true, config.isPostBindingAuthnRequest());
         assertEquals(true, config.isPostBindingResponse());
         assertEquals(true, config.isValidateSignature());
+        assertEquals(false, config.isAddExtensionsElementWithKeyInfo());
     }
 
     private void assertOidcIdentityProviderConfig(IdentityProviderModel identityProvider) {
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigPostNoIdpKeyServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigPostNoIdpKeyServlet.java
new file mode 100644
index 0000000..5ef40ae
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigPostNoIdpKeyServlet.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.adapter.page;
+
+import org.jboss.arquillian.container.test.api.OperateOnDeployment;
+import org.jboss.arquillian.test.api.ArquillianResource;
+
+import java.net.URL;
+
+/**
+ * @author hmlnarik
+ */
+public class EmployeeSigPostNoIdpKeyServlet extends SAMLServlet {
+    public static final String DEPLOYMENT_NAME = "employee-sig-post-noidpkey";
+
+    @ArquillianResource
+    @OperateOnDeployment(DEPLOYMENT_NAME)
+    private URL url;
+
+    @Override
+    public URL getInjectedUrl() {
+        return url;
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigRedirNoIdpKeyServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigRedirNoIdpKeyServlet.java
new file mode 100644
index 0000000..ac6d671
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigRedirNoIdpKeyServlet.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.adapter.page;
+
+import org.jboss.arquillian.container.test.api.OperateOnDeployment;
+import org.jboss.arquillian.test.api.ArquillianResource;
+
+import java.net.URL;
+
+/**
+ * @author hmlnarik
+ */
+public class EmployeeSigRedirNoIdpKeyServlet extends SAMLServlet {
+    public static final String DEPLOYMENT_NAME = "employee-sig-redir-noidpkey";
+
+    @ArquillianResource
+    @OperateOnDeployment(DEPLOYMENT_NAME)
+    private URL url;
+
+    @Override
+    public URL getInjectedUrl() {
+        return url;
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigRedirOptNoIdpKeyServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigRedirOptNoIdpKeyServlet.java
new file mode 100644
index 0000000..e37c12f
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeSigRedirOptNoIdpKeyServlet.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.adapter.page;
+
+import org.jboss.arquillian.container.test.api.OperateOnDeployment;
+import org.jboss.arquillian.test.api.ArquillianResource;
+
+import java.net.URL;
+
+/**
+ * @author hmlnarik
+ */
+public class EmployeeSigRedirOptNoIdpKeyServlet extends SAMLServlet {
+    public static final String DEPLOYMENT_NAME = "employee-sig-redir-opt-noidpkey";
+
+    @ArquillianResource
+    @OperateOnDeployment(DEPLOYMENT_NAME)
+    private URL url;
+
+    @Override
+    public URL getInjectedUrl() {
+        return url;
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
index 2906778..6dd3ee3 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java
@@ -24,6 +24,12 @@ import org.junit.Assert;
 import org.junit.Test;
 import org.keycloak.admin.client.resource.ClientResource;
 import org.keycloak.admin.client.resource.ProtocolMappersResource;
+import org.keycloak.common.util.KeyUtils;
+import org.keycloak.common.util.PemUtils;
+import org.keycloak.keys.Attributes;
+import org.keycloak.keys.KeyProvider;
+import org.keycloak.keys.RsaKeyProviderFactory;
+import org.keycloak.representations.idm.ComponentRepresentation;
 import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
 import org.keycloak.protocol.saml.mappers.RoleListMapper;
 import org.keycloak.representations.idm.ClientRepresentation;
@@ -40,6 +46,9 @@ import org.keycloak.testsuite.adapter.page.BadRealmSalesPostSigServlet;
 import org.keycloak.testsuite.adapter.page.Employee2Servlet;
 import org.keycloak.testsuite.adapter.page.EmployeeServlet;
 import org.keycloak.testsuite.adapter.page.EmployeeSigFrontServlet;
+import org.keycloak.testsuite.adapter.page.EmployeeSigPostNoIdpKeyServlet;
+import org.keycloak.testsuite.adapter.page.EmployeeSigRedirNoIdpKeyServlet;
+import org.keycloak.testsuite.adapter.page.EmployeeSigRedirOptNoIdpKeyServlet;
 import org.keycloak.testsuite.adapter.page.EmployeeSigServlet;
 import org.keycloak.testsuite.adapter.page.InputPortal;
 import org.keycloak.testsuite.adapter.page.MissingAssertionSig;
@@ -80,6 +89,8 @@ import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.net.URI;
 import java.net.URL;
+import java.security.KeyPair;
+import java.security.PublicKey;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -110,6 +121,15 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
     protected EmployeeSigServlet employeeSigServletPage;
 
     @Page
+    protected EmployeeSigPostNoIdpKeyServlet employeeSigPostNoIdpKeyServletPage;
+
+    @Page
+    protected EmployeeSigRedirNoIdpKeyServlet employeeSigRedirNoIdpKeyServletPage;
+
+    @Page
+    protected EmployeeSigRedirOptNoIdpKeyServlet employeeSigRedirOptNoIdpKeyServletPage;
+
+    @Page
     protected EmployeeSigFrontServlet employeeSigFrontServletPage;
 
     @Page
@@ -184,6 +204,21 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
         return samlServletDeployment(EmployeeSigServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
     }
 
+    @Deployment(name = EmployeeSigPostNoIdpKeyServlet.DEPLOYMENT_NAME)
+    protected static WebArchive employeeSigPostNoIdpKeyServlet() {
+        return samlServletDeployment(EmployeeSigPostNoIdpKeyServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
+    }
+
+    @Deployment(name = EmployeeSigRedirNoIdpKeyServlet.DEPLOYMENT_NAME)
+    protected static WebArchive employeeSigRedirNoIdpKeyServlet() {
+        return samlServletDeployment(EmployeeSigRedirNoIdpKeyServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
+    }
+
+    @Deployment(name = EmployeeSigRedirOptNoIdpKeyServlet.DEPLOYMENT_NAME)
+    protected static WebArchive employeeSigRedirOptNoIdpKeyServlet() {
+        return samlServletDeployment(EmployeeSigRedirOptNoIdpKeyServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
+    }
+
     @Deployment(name = EmployeeSigFrontServlet.DEPLOYMENT_NAME)
     protected static WebArchive employeeSigFront() {
         return samlServletDeployment(EmployeeSigFrontServlet.DEPLOYMENT_NAME, SendUsernameServlet.class);
@@ -394,6 +429,69 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
         testSuccessfulAndUnauthorizedLogin(employeeSigServletPage, testRealmSAMLRedirectLoginPage);
     }
 
+    private PublicKey createKeys(String priority) throws Exception {
+        KeyPair keyPair = KeyUtils.generateRsaKeyPair(1024);
+        String privateKeyPem = PemUtils.encodeKey(keyPair.getPrivate());
+        PublicKey publicKey = keyPair.getPublic();
+
+        ComponentRepresentation rep = new ComponentRepresentation();
+        rep.setName("mycomponent");
+        rep.setParentId("demo");
+        rep.setProviderId(RsaKeyProviderFactory.ID);
+        rep.setProviderType(KeyProvider.class.getName());
+
+        org.keycloak.common.util.MultivaluedHashMap config = new org.keycloak.common.util.MultivaluedHashMap();
+        config.addFirst("priority", priority);
+        config.addFirst(Attributes.PRIVATE_KEY_KEY, privateKeyPem);
+        rep.setConfig(config);
+
+        testRealmResource().components().add(rep);
+
+        return publicKey;
+    }
+
+    private void dropKeys(String priority) {
+        for (ComponentRepresentation c : testRealmResource().components().query("demo", KeyProvider.class.getName())) {
+            if (c.getConfig().getFirst("priority").equals(priority)) {
+                testRealmResource().components().component(c.getId()).remove();
+                return;
+            }
+        }
+        throw new RuntimeException("Failed to find keys");
+    }
+
+    private void testRotatedKeysPropagated(SAMLServlet servletPage, Login loginPage) throws Exception {
+        boolean keyDropped = false;
+        try {
+            log.info("Creating new key");
+            createKeys("1000");
+            testSuccessfulAndUnauthorizedLogin(servletPage, loginPage);
+            log.info("Dropping new key");
+            dropKeys("1000");
+            keyDropped = true;
+            testSuccessfulAndUnauthorizedLogin(servletPage, loginPage);
+        } finally {
+            if (! keyDropped) {
+                dropKeys("1000");
+            }
+        }
+    }
+
+    @Test
+    public void employeeSigPostNoIdpKeyTest() throws Exception {
+        testRotatedKeysPropagated(employeeSigPostNoIdpKeyServletPage, testRealmSAMLPostLoginPage);
+    }
+
+    @Test
+    public void employeeSigRedirNoIdpKeyTest() throws Exception {
+        testRotatedKeysPropagated(employeeSigRedirNoIdpKeyServletPage, testRealmSAMLRedirectLoginPage);
+    }
+
+    @Test
+    public void employeeSigRedirOptNoIdpKeyTest() throws Exception {
+        testRotatedKeysPropagated(employeeSigRedirOptNoIdpKeyServletPage, testRealmSAMLRedirectLoginPage);
+    }
+
     @Test
     public void employeeSigFrontTest() {
         testSuccessfulAndUnauthorizedLogin(employeeSigFrontServletPage, testRealmSAMLRedirectLoginPage);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java
index 626c0d4..4328c8f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java
@@ -21,13 +21,11 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.keycloak.admin.client.resource.ClientResource;
-import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.testsuite.admin.ApiUtil;
 import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.keycloak.testsuite.auth.page.AuthRealm.TEST;
+import static org.junit.Assert.assertThat;
+import static org.hamcrest.Matchers.*;
 
 /**
  * Test getting the installation/configuration files for OIDC and SAML.
@@ -71,7 +69,7 @@ public class InstallationTest extends AbstractClientTest {
     public void testOidcJBossXml() {
         String xml = oidcClient.getInstallationProvider("keycloak-oidc-jboss-subsystem");
         assertOidcInstallationConfig(xml);
-        assertTrue(xml.contains("<secure-deployment"));
+        assertThat(xml, containsString("<secure-deployment"));
     }
 
     @Test
@@ -81,43 +79,43 @@ public class InstallationTest extends AbstractClientTest {
     }
 
     private void assertOidcInstallationConfig(String config) {
-        assertTrue(config.contains("master"));
-        assertFalse(config.contains(ApiUtil.findActiveKey(testRealmResource()).getPublicKey()));
-        assertTrue(config.contains(authServerUrl()));
+        assertThat(config, containsString("master"));
+        assertThat(config, not(containsString(ApiUtil.findActiveKey(testRealmResource()).getPublicKey())));
+        assertThat(config, containsString(authServerUrl()));
     }
 
     @Test
     public void testSamlMetadataIdpDescriptor() {
         String xml = samlClient.getInstallationProvider("saml-idp-descriptor");
-        assertTrue(xml.contains("<EntityDescriptor"));
-        assertTrue(xml.contains("<IDPSSODescriptor"));
-        assertTrue(xml.contains(ApiUtil.findActiveKey(testRealmResource()).getCertificate()));
-        assertTrue(xml.contains(samlUrl()));
+        assertThat(xml, containsString("<EntityDescriptor"));
+        assertThat(xml, containsString("<IDPSSODescriptor"));
+        assertThat(xml, containsString(ApiUtil.findActiveKey(testRealmResource()).getCertificate()));
+        assertThat(xml, containsString(samlUrl()));
     }
 
     @Test
     public void testSamlAdapterXml() {
         String xml = samlClient.getInstallationProvider("keycloak-saml");
-        assertTrue(xml.contains("<keycloak-saml-adapter>"));
-        assertTrue(xml.contains(SAML_NAME));
-        assertTrue(xml.contains(ApiUtil.findActiveKey(testRealmResource()).getCertificate()));
-        assertTrue(xml.contains(samlUrl()));
+        assertThat(xml, containsString("<keycloak-saml-adapter>"));
+        assertThat(xml, containsString(SAML_NAME));
+        assertThat(xml, not(containsString(ApiUtil.findActiveKey(testRealmResource()).getCertificate())));
+        assertThat(xml, containsString(samlUrl()));
     }
 
     @Test
     public void testSamlMetadataSpDescriptor() {
         String xml = samlClient.getInstallationProvider("saml-sp-descriptor");
-        assertTrue(xml.contains("<EntityDescriptor"));
-        assertTrue(xml.contains("<SPSSODescriptor"));
-        assertTrue(xml.contains(SAML_NAME));
+        assertThat(xml, containsString("<EntityDescriptor"));
+        assertThat(xml, containsString("<SPSSODescriptor"));
+        assertThat(xml, containsString(SAML_NAME));
     }
 
     @Test
     public void testSamlJBossXml() {
         String xml = samlClient.getInstallationProvider("keycloak-saml-subsystem");
-        assertTrue(xml.contains("<secure-deployment"));
-        assertTrue(xml.contains(SAML_NAME));
-        assertTrue(xml.contains(ApiUtil.findActiveKey(testRealmResource()).getCertificate()));
-        assertTrue(xml.contains(samlUrl()));
+        assertThat(xml, containsString("<secure-deployment"));
+        assertThat(xml, containsString(SAML_NAME));
+        assertThat(xml, not(containsString(ApiUtil.findActiveKey(testRealmResource()).getCertificate())));
+        assertThat(xml, containsString(samlUrl()));
     }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java
index e3392b3..f550c17 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java
@@ -57,17 +57,43 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import javax.xml.crypto.dsig.XMLSignature;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.hamcrest.Matchers.*;
+import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType;
+import org.w3c.dom.NodeList;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
 public class IdentityProviderTest extends AbstractAdminTest {
 
+    // Certificate imported from
+    private static final String SIGNING_CERT_1 = "MIICmzCCAYMCBgFUYnC0OjANBgkqhkiG9w0BAQsFADARMQ8wDQY"
+      + "DVQQDDAZtYXN0ZXIwHhcNMTYwNDI5MTQzMjEzWhcNMjYwNDI5MTQzMzUzWjARMQ8wDQYDVQQDDAZtYXN0ZXI"
+      + "wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCN25AW1poMEZRbuMAHG58AThZmCwMV6/Gcui4mjGa"
+      + "cRFyudgqzLjQ2rxpoW41JAtLjbjeAhuWvirUcFVcOeS3gM/ZC27qCpYighAcylZz6MYocnEe1+e8rPPk4JlI"
+      + "D6Wv62dgu+pL/vYsQpRhvD3Y2c/ytgr5D32xF+KnzDehUy5BSyzypvu12Wq9mS5vK5tzkN37EjkhpY2ZxaXP"
+      + "ubjDIITCAL4Q8M/m5IlacBaUZbzI4AQrHnMP1O1IH2dHSWuMiBe+xSDTco72PmuYPJKTV4wQdeBUIkYbfLc4"
+      + "RxVmXEvgkQgyW86EoMPxlWJpj7+mTIR+l+2thZPr/VgwTs82rAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAA/"
+      + "Ip/Hi8RoVu5ouaFFlc5whT7ltuK8slfLGW4tM4vJXhInYwsqIRQKBNDYW/64xle3eII4u1yAH1OYRRwEs7Em"
+      + "1pr4QuFuTY1at+aE0sE46XDlyESI0txJjWxYoT133vM0We2pj1b2nxgU30rwjKA3whnKEfTEYT/n3JBSqNgg"
+      + "y6l8ZGw/oPSgvPaR4+xeB1tfQFC4VrLoYKoqH6hAL530nKxL+qV8AIfL64NDEE8ankIAEDAAFe8x3CPUfXR/"
+      + "p4KOANKkpz8ieQaHDb1eITkAwUwjESj6UF9D1aePlhWls/HX0gujFXtWfWfrJ8CU/ogwlH8y1jgRuLjFQYZk6llc=";
+
+    private static final String SIGNING_CERT_2 = "MIIBnDCCAQUCBgFYKXKsPTANBgkqhkiG9w0BAQsFADAUMRIwEAY"
+      + "DVQQDDAlzYW1sLWRlbW8wHhcNMTYxMTAzMDkwNzEwWhcNMjYxMTAzMDkwODUwWjAUMRIwEAYDVQQDDAlzYW1"
+      + "sLWRlbW8wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKtWsK5O0CtuBpnMvWG+HTG0vmZzujQ2o9WdheQ"
+      + "u+BzCILcGMsbDW0YQaglpcO5JpGWWhubnckGGPHfdQ2/7nP9QwbiTK0FbGF41UqcvoaCqU1psxoV88s8IXyQ"
+      + "CAqeyLv00yj6foqdJjxh5SZ5z+na+M7Y2OxIBVxYRAxWEnfUvAgMBAAEwDQYJKoZIhvcNAQELBQADgYEAhet"
+      + "vOU8TyqfZF5jpv0IcrviLl/DoFrbjByeHR+pu/vClcAOjL/u7oQELuuTfNsBI4tpexUj5G8q/YbEz0gk7idf"
+      + "LXrAUVcsR73oTngrhRfwUSmPrjjK0kjcRb6HL9V/+wh3R/6mEd59U08ExT8N38rhmn0CI3ehMdebReprP7U8=";
+
     @Test
     public void testFindAll() {
         create(createRep("google", "google"));
@@ -303,7 +329,45 @@ public class IdentityProviderTest extends AbstractAdminTest {
         form.addFormData("file", body, MediaType.APPLICATION_XML_TYPE, "saml-idp-metadata.xml");
 
         Map<String, String> result = realm.identityProviders().importFrom(form);
-        assertSamlImport(result);
+        assertSamlImport(result, SIGNING_CERT_1);
+
+        // Create new SAML identity provider using configuration retrieved from import-config
+        create(createRep("saml", "saml", result));
+
+        IdentityProviderResource provider = realm.identityProviders().get("saml");
+        IdentityProviderRepresentation rep = provider.toRepresentation();
+        assertCreatedSamlIdp(rep);
+
+        // Now list the providers - we should see the one just created
+        List<IdentityProviderRepresentation> providers = realm.identityProviders().findAll();
+        Assert.assertNotNull("identityProviders not null", providers);
+        Assert.assertEquals("identityProviders instance count", 1, providers.size());
+        assertEqual(rep, providers.get(0));
+
+        // Perform export, and make sure some of the values are like they're supposed to be
+        Response response = realm.identityProviders().get("saml").export("xml");
+        Assert.assertEquals(200, response.getStatus());
+        body = response.readEntity(String.class);
+        response.close();
+
+        assertSamlExport(body);
+    }
+
+    @Test
+    public void testSamlImportAndExportMultipleSigningKeys() throws URISyntaxException, IOException, ParsingException {
+
+        // Use import-config to convert IDPSSODescriptor file into key value pairs
+        // to use when creating a SAML Identity Provider
+        MultipartFormDataOutput form = new MultipartFormDataOutput();
+        form.addFormData("providerId", "saml", MediaType.TEXT_PLAIN_TYPE);
+
+        URL idpMeta = getClass().getClassLoader().getResource("admin-test/saml-idp-metadata-two-signing-certs.xml");
+        byte [] content = Files.readAllBytes(Paths.get(idpMeta.toURI()));
+        String body = new String(content, Charset.forName("utf-8"));
+        form.addFormData("file", body, MediaType.APPLICATION_XML_TYPE, "saml-idp-metadata-two-signing-certs");
+
+        Map<String, String> result = realm.identityProviders().importFrom(form);
+        assertSamlImport(result, SIGNING_CERT_1 + "," + SIGNING_CERT_2);
 
         // Create new SAML identity provider using configuration retrieved from import-config
         create(createRep("saml", "saml", result));
@@ -464,18 +528,29 @@ public class IdentityProviderTest extends AbstractAdminTest {
         // import endpoint simply converts IDPSSODescriptor into key value pairs.
         // check that saml-idp-metadata.xml was properly converted into key value pairs
         //System.out.println(config);
-        Assert.assertEquals("Config size", 7, config.size());
-        Assert.assertEquals("validateSignature", "true", config.get("validateSignature"));
-        Assert.assertEquals("singleLogoutServiceUrl", "http://localhost:8080/auth/realms/master/protocol/saml", config.get("singleLogoutServiceUrl"));
-        Assert.assertEquals("postBindingResponse", "true", config.get("postBindingResponse"));
-        Assert.assertEquals("postBindingAuthnRequest", "true", config.get("postBindingAuthnRequest"));
-        Assert.assertEquals("singleSignOnServiceUrl", "http://localhost:8080/auth/realms/master/protocol/saml", config.get("singleSignOnServiceUrl"));
-        Assert.assertEquals("wantAuthnRequestsSigned", "true", config.get("wantAuthnRequestsSigned"));
-        Assert.assertNotNull("signingCertificate not null", config.get("signingCertificate"));
+        assertThat(config.keySet(), containsInAnyOrder(
+          "validateSignature",
+          "singleLogoutServiceUrl",
+          "postBindingResponse",
+          "postBindingAuthnRequest",
+          "singleSignOnServiceUrl",
+          "wantAuthnRequestsSigned",
+          "signingCertificate",
+          "addExtensionsElementWithKeyInfo"
+        ));
+        assertThat(config, hasEntry("validateSignature", "true"));
+        assertThat(config, hasEntry("singleLogoutServiceUrl", "http://localhost:8080/auth/realms/master/protocol/saml"));
+        assertThat(config, hasEntry("postBindingResponse", "true"));
+        assertThat(config, hasEntry("postBindingAuthnRequest", "true"));
+        assertThat(config, hasEntry("singleSignOnServiceUrl", "http://localhost:8080/auth/realms/master/protocol/saml"));
+        assertThat(config, hasEntry("wantAuthnRequestsSigned", "true"));
+        assertThat(config, hasEntry("addExtensionsElementWithKeyInfo", "false"));
+        assertThat(config, hasEntry(is("signingCertificate"), notNullValue()));
     }
 
-    private void assertSamlImport(Map<String, String> config) {
+    private void assertSamlImport(Map<String, String> config, String expectedSigningCertificates) {
         assertSamlConfig(config);
+        assertThat(config, hasEntry("signingCertificate", expectedSigningCertificates));
     }
 
     private void assertSamlExport(String body) throws ParsingException, URISyntaxException {
@@ -534,7 +609,11 @@ public class IdentityProviderTest extends AbstractAdminTest {
 
         Assert.assertNotNull("KeyDescriptor not null", desc.getKeyDescriptor());
         Assert.assertEquals("KeyDescriptor.size", 1, desc.getKeyDescriptor().size());
-        Assert.assertEquals("KeyDescriptor.Use", KeyTypes.SIGNING, desc.getKeyDescriptor().get(0).getUse());
+        KeyDescriptorType keyDesc = desc.getKeyDescriptor().get(0);
+        assertThat(keyDesc, notNullValue());
+        assertThat(keyDesc.getUse(), equalTo(KeyTypes.SIGNING));
+        NodeList cert = keyDesc.getKeyInfo().getElementsByTagNameNS(XMLSignature.XMLNS, "X509Certificate");
+        assertThat("KeyDescriptor.Signing.Cert existence", cert.getLength(), is(1));
     }
 
     private void assertProviderInfo(Map<String, String> info, String id, String name) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-post-noidpkey/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-post-noidpkey/WEB-INF/keycloak-saml.xml
new file mode 100644
index 0000000..acd3d9d
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-post-noidpkey/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,54 @@
+<!--
+  ~ 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>
+    <SP entityID="http://localhost:8081/employee-sig-post-noidpkey/"
+        sslPolicy="EXTERNAL"
+        logoutPage="/logout.jsp"
+        nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+        forceAuthentication="false">
+        <Keys>
+            <Key signing="true" >
+                <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                    <PrivateKey alias="http://localhost:8080/employee-sig/" password="test123"/>
+                    <Certificate alias="http://localhost:8080/employee-sig/"/>
+                </KeyStore>
+            </Key>
+        </Keys>
+        <PrincipalNameMapping policy="FROM_NAME_ID"/>
+        <RoleIdentifiers>
+            <Attribute name="Role"/>
+        </RoleIdentifiers>
+        <IDP entityID="idp">
+            <SingleSignOnService signRequest="true"
+                                 validateResponseSignature="true"
+                                 requestBinding="POST"
+                                 bindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+                    />
+
+            <SingleLogoutService
+                    validateRequestSignature="true"
+                    validateResponseSignature="true"
+                    signRequest="true"
+                    signResponse="true"
+                    requestBinding="POST"
+                    responseBinding="POST"
+                    postBindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+                    />
+        </IDP>
+     </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-post-noidpkey/WEB-INF/keystore.jks b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-post-noidpkey/WEB-INF/keystore.jks
new file mode 100644
index 0000000..4daad21
Binary files /dev/null and b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-post-noidpkey/WEB-INF/keystore.jks differ
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-noidpkey/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-noidpkey/WEB-INF/keycloak-saml.xml
new file mode 100644
index 0000000..c134693
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-noidpkey/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,54 @@
+<!--
+  ~ 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>
+    <SP entityID="http://localhost:8081/employee-sig-redir-noidpkey/"
+        sslPolicy="EXTERNAL"
+        logoutPage="/logout.jsp"
+        nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+        forceAuthentication="false">
+        <Keys>
+            <Key signing="true" >
+                <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                    <PrivateKey alias="http://localhost:8080/employee-sig/" password="test123"/>
+                    <Certificate alias="http://localhost:8080/employee-sig/"/>
+                </KeyStore>
+            </Key>
+        </Keys>
+        <PrincipalNameMapping policy="FROM_NAME_ID"/>
+        <RoleIdentifiers>
+            <Attribute name="Role"/>
+        </RoleIdentifiers>
+        <IDP entityID="idp">
+            <SingleSignOnService signRequest="true"
+                                 validateResponseSignature="true"
+                                 requestBinding="REDIRECT"
+                                 bindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+                    />
+
+            <SingleLogoutService
+                    validateRequestSignature="true"
+                    validateResponseSignature="true"
+                    signRequest="true"
+                    signResponse="true"
+                    requestBinding="REDIRECT"
+                    responseBinding="REDIRECT"
+                    redirectBindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+                    />
+        </IDP>
+     </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-noidpkey/WEB-INF/keystore.jks b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-noidpkey/WEB-INF/keystore.jks
new file mode 100644
index 0000000..4daad21
Binary files /dev/null and b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-noidpkey/WEB-INF/keystore.jks differ
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-opt-noidpkey/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-opt-noidpkey/WEB-INF/keycloak-saml.xml
new file mode 100644
index 0000000..8df82ae
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-opt-noidpkey/WEB-INF/keycloak-saml.xml
@@ -0,0 +1,54 @@
+<!--
+  ~ 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>
+    <SP entityID="http://localhost:8081/employee-sig-redir-opt-noidpkey/"
+        sslPolicy="EXTERNAL"
+        logoutPage="/logout.jsp"
+        nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+        forceAuthentication="false">
+        <Keys>
+            <Key signing="true" >
+                <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+                    <PrivateKey alias="http://localhost:8080/employee-sig/" password="test123"/>
+                    <Certificate alias="http://localhost:8080/employee-sig/"/>
+                </KeyStore>
+            </Key>
+        </Keys>
+        <PrincipalNameMapping policy="FROM_NAME_ID"/>
+        <RoleIdentifiers>
+            <Attribute name="Role"/>
+        </RoleIdentifiers>
+        <IDP entityID="idp">
+            <SingleSignOnService signRequest="true"
+                                 validateResponseSignature="true"
+                                 requestBinding="REDIRECT"
+                                 bindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+                    />
+
+            <SingleLogoutService
+                    validateRequestSignature="true"
+                    validateResponseSignature="true"
+                    signRequest="true"
+                    signResponse="true"
+                    requestBinding="REDIRECT"
+                    responseBinding="REDIRECT"
+                    redirectBindingUrl="http://localhost:8080/auth/realms/demo/protocol/saml"
+                    />
+        </IDP>
+     </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-opt-noidpkey/WEB-INF/keystore.jks b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-opt-noidpkey/WEB-INF/keystore.jks
new file mode 100644
index 0000000..4daad21
Binary files /dev/null and b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee-sig-redir-opt-noidpkey/WEB-INF/keystore.jks differ
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json
index 0e25d89..cca23fc 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json
@@ -334,6 +334,61 @@
             }
         },
         {
+            "clientId": "http://localhost:8081/employee-sig-redir-noidpkey/",
+            "enabled": true,
+            "protocol": "saml",
+            "fullScopeAllowed": true,
+            "baseUrl": "http://localhost:8080/employee-sig-redir-noidpkey",
+            "redirectUris": [
+                "http://localhost:8080/employee-sig-redir-noidpkey/*"
+            ],
+            "adminUrl": "http://localhost:8080/employee-sig-redir-noidpkey",
+            "attributes": {
+                "saml.server.signature": "true",
+                "saml.client.signature": "true",
+                "saml.signature.algorithm": "RSA_SHA256",
+                "saml.authnstatement": "true",
+                "saml.signing.certificate": "MIIB0DCCATkCBgFJH5u0EDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNodHRwOi8vbG9jYWxob3N0OjgwODAvZW1wbG95ZWUtc2lnLzAeFw0xNDEwMTcxOTMzNThaFw0yNDEwMTcxOTM1MzhaMC4xLDAqBgNVBAMTI2h0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9lbXBsb3llZS1zaWcvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+9kVgPFpshjS2aT2g52lqTv2lqb1jgvXZVk7iFF4LAO6SdCXKXRZI4SuzIRkVNpE1a42V1kQRlaozoFklgvX5sje8tkpa9ylq+bxGXM9RRycqRu2B+oWUV7Aqq7Bs0Xud0WeHQYRcEoCjqsFKGy65qkLRDdT70FTJgpSHts+gDwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBACKyPLGqMX8GsIrCfJU8eVnpaqzTXMglLVo/nTcfAnWe9UAdVe8N3a2PXpDBvuqNA/DEAhVcQgxdlOTWnB6s8/yLTRuH0bZgb3qGdySif+lU+E7zZ/SiDzavAvn+ABqemnzHcHyhYO+hNRGHvUbW5OAii9Vdjhm8BI32YF1NwhKp"
+            }
+        },
+        {
+            "clientId": "http://localhost:8081/employee-sig-redir-opt-noidpkey/",
+            "enabled": true,
+            "protocol": "saml",
+            "fullScopeAllowed": true,
+            "baseUrl": "http://localhost:8080/employee-sig-redir-opt-noidpkey",
+            "redirectUris": [
+                "http://localhost:8080/employee-sig-redir-opt-noidpkey/*"
+            ],
+            "adminUrl": "http://localhost:8080/employee-sig-redir-opt-noidpkey",
+            "attributes": {
+                "saml.server.signature": "true",
+                "saml.server.signature.keyinfo.ext": "true",
+                "saml.client.signature": "true",
+                "saml.signature.algorithm": "RSA_SHA256",
+                "saml.authnstatement": "true",
+                "saml.signing.certificate": "MIIB0DCCATkCBgFJH5u0EDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNodHRwOi8vbG9jYWxob3N0OjgwODAvZW1wbG95ZWUtc2lnLzAeFw0xNDEwMTcxOTMzNThaFw0yNDEwMTcxOTM1MzhaMC4xLDAqBgNVBAMTI2h0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9lbXBsb3llZS1zaWcvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+9kVgPFpshjS2aT2g52lqTv2lqb1jgvXZVk7iFF4LAO6SdCXKXRZI4SuzIRkVNpE1a42V1kQRlaozoFklgvX5sje8tkpa9ylq+bxGXM9RRycqRu2B+oWUV7Aqq7Bs0Xud0WeHQYRcEoCjqsFKGy65qkLRDdT70FTJgpSHts+gDwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBACKyPLGqMX8GsIrCfJU8eVnpaqzTXMglLVo/nTcfAnWe9UAdVe8N3a2PXpDBvuqNA/DEAhVcQgxdlOTWnB6s8/yLTRuH0bZgb3qGdySif+lU+E7zZ/SiDzavAvn+ABqemnzHcHyhYO+hNRGHvUbW5OAii9Vdjhm8BI32YF1NwhKp"
+            }
+        },
+        {
+            "clientId": "http://localhost:8081/employee-sig-post-noidpkey/",
+            "enabled": true,
+            "protocol": "saml",
+            "fullScopeAllowed": true,
+            "baseUrl": "http://localhost:8080/employee-sig-post-noidpkey",
+            "redirectUris": [
+                "http://localhost:8080/employee-sig-post-noidpkey/*"
+            ],
+            "adminUrl": "http://localhost:8080/employee-sig-post-noidpkey",
+            "attributes": {
+                "saml.server.signature": "true",
+                "saml.client.signature": "true",
+                "saml.signature.algorithm": "RSA_SHA256",
+                "saml.authnstatement": "true",
+                "saml.signing.certificate": "MIIB0DCCATkCBgFJH5u0EDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNodHRwOi8vbG9jYWxob3N0OjgwODAvZW1wbG95ZWUtc2lnLzAeFw0xNDEwMTcxOTMzNThaFw0yNDEwMTcxOTM1MzhaMC4xLDAqBgNVBAMTI2h0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9lbXBsb3llZS1zaWcvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+9kVgPFpshjS2aT2g52lqTv2lqb1jgvXZVk7iFF4LAO6SdCXKXRZI4SuzIRkVNpE1a42V1kQRlaozoFklgvX5sje8tkpa9ylq+bxGXM9RRycqRu2B+oWUV7Aqq7Bs0Xud0WeHQYRcEoCjqsFKGy65qkLRDdT70FTJgpSHts+gDwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBACKyPLGqMX8GsIrCfJU8eVnpaqzTXMglLVo/nTcfAnWe9UAdVe8N3a2PXpDBvuqNA/DEAhVcQgxdlOTWnB6s8/yLTRuH0bZgb3qGdySif+lU+E7zZ/SiDzavAvn+ABqemnzHcHyhYO+hNRGHvUbW5OAii9Vdjhm8BI32YF1NwhKp"
+            }
+        },
+        {
             "clientId": "http://localhost:8081/employee/",
             "enabled": true,
             "protocol": "saml",
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/saml-idp-metadata.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/saml-idp-metadata.xml
index 2bcfc21..f28e206 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/saml-idp-metadata.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/saml-idp-metadata.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <EntityDescriptor entityID="http://localhost:8080/auth/realms/master"
                   xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                  xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"
 >
    <IDPSSODescriptor WantAuthnRequestsSigned="true"
       protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/saml-idp-metadata-two-signing-certs.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/saml-idp-metadata-two-signing-certs.xml
new file mode 100644
index 0000000..dba0d5a
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/saml-idp-metadata-two-signing-certs.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<EntityDescriptor entityID="http://localhost:8080/auth/realms/master"
+                  xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+                  xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"
+>
+   <IDPSSODescriptor WantAuthnRequestsSigned="true"
+      protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+   <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:8080/auth/realms/master/protocol/saml" />
+      <SingleLogoutService
+         Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+         Location="http://localhost:8080/auth/realms/master/protocol/saml" />
+         <KeyDescriptor use="signing">
+             <dsig:KeyInfo>
+                 <dsig:KeyName>hAoy_sBtpu6FdRVCk7ykihF6Ug-o0pKPK3LN9RYkeqs</dsig:KeyName>
+                 <dsig:X509Data>
+                     <dsig:X509Certificate>
+                         MIICmzCCAYMCBgFUYnC0OjANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMTYwNDI5MTQzMjEzWhcNMjYwNDI5MTQzMzUzWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCN25AW1poMEZRbuMAHG58AThZmCwMV6/Gcui4mjGacRFyudgqzLjQ2rxpoW41JAtLjbjeAhuWvirUcFVcOeS3gM/ZC27qCpYighAcylZz6MYocnEe1+e8rPPk4JlID6Wv62dgu+pL/vYsQpRhvD3Y2c/ytgr5D32xF+KnzDehUy5BSyzypvu12Wq9mS5vK5tzkN37EjkhpY2ZxaXPubjDIITCAL4Q8M/m5IlacBaUZbzI4AQrHnMP1O1IH2dHSWuMiBe+xSDTco72PmuYPJKTV4wQdeBUIkYbfLc4RxVmXEvgkQgyW86EoMPxlWJpj7+mTIR+l+2thZPr/VgwTs82rAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAA/Ip/Hi8RoVu5ouaFFlc5whT7ltuK8slfLGW4tM4vJXhInYwsqIRQKBNDYW/64xle3eII4u1yAH1OYRRwEs7Em1pr4QuFuTY1at+aE0sE46XDlyESI0txJjWxYoT133vM0We2pj1b2nxgU30rwjKA3whnKEfTEYT/n3JBSqNggy6l8ZGw/oPSgvPaR4+xeB1tfQFC4VrLoYKoqH6hAL530nKxL+qV8AIfL64NDEE8ankIAEDAAFe8x3CPUfXR/p4KOANKkpz8ieQaHDb1eITkAwUwjESj6UF9D1aePlhWls/HX0gujFXtWfWfrJ8CU/ogwlH8y1jgRuLjFQYZk6llc=
+                     </dsig:X509Certificate>
+                 </dsig:X509Data>
+             </dsig:KeyInfo>
+         </KeyDescriptor>
+         <KeyDescriptor use="signing">
+             <dsig:KeyInfo>
+                 <dsig:KeyName>FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE</dsig:KeyName>
+                 <dsig:X509Data>
+                     <dsig:X509Certificate>
+                         MIIBnDCCAQUCBgFYKXKsPTANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlzYW1sLWRlbW8wHhcNMTYxMTAzMDkwNzEwWhcNMjYxMTAzMDkwODUwWjAUMRIwEAYDVQQDDAlzYW1sLWRlbW8wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKtWsK5O0CtuBpnMvWG+HTG0vmZzujQ2o9WdheQu+BzCILcGMsbDW0YQaglpcO5JpGWWhubnckGGPHfdQ2/7nP9QwbiTK0FbGF41UqcvoaCqU1psxoV88s8IXyQCAqeyLv00yj6foqdJjxh5SZ5z+na+M7Y2OxIBVxYRAxWEnfUvAgMBAAEwDQYJKoZIhvcNAQELBQADgYEAhetvOU8TyqfZF5jpv0IcrviLl/DoFrbjByeHR+pu/vClcAOjL/u7oQELuuTfNsBI4tpexUj5G8q/YbEz0gk7idfLXrAUVcsR73oTngrhRfwUSmPrjjK0kjcRb6HL9V/+wh3R/6mEd59U08ExT8N38rhmn0CI3ehMdebReprP7U8=
+                     </dsig:X509Certificate>
+                 </dsig:X509Data>
+             </dsig:KeyInfo>
+         </KeyDescriptor>
+   </IDPSSODescriptor>
+</EntityDescriptor>
diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/settings/ClientSettingsForm.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/settings/ClientSettingsForm.java
index 79092cd..b8cbf4d 100644
--- a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/settings/ClientSettingsForm.java
+++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/clients/settings/ClientSettingsForm.java
@@ -223,6 +223,7 @@ public class ClientSettingsForm extends CreateClientForm {
 	public static final String SAML_FORCE_POST_BINDING = "saml.force.post.binding";
 	public static final String SAML_MULTIVALUED_ROLES = "saml.multivalued.roles";
 	public static final String SAML_SERVER_SIGNATURE = "saml.server.signature";
+	public static final String SAML_SERVER_SIGNATURE_KEYINFO_EXT = "saml.server.signature.keyinfo.ext";
 	public static final String SAML_SIGNATURE_ALGORITHM = "saml.signature.algorithm";
 	public static final String SAML_ASSERTION_CONSUMER_URL_POST = "saml_assertion_consumer_url_post";
 	public static final String SAML_ASSERTION_CONSUMER_URL_REDIRECT = "saml_assertion_consumer_url_redirect";
@@ -236,6 +237,8 @@ public class ClientSettingsForm extends CreateClientForm {
         private OnOffSwitch samlAuthnStatement;
         @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='samlServerSignature']]")
         private OnOffSwitch samlServerSignature;
+        @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='samlServerSignatureEnableKeyInfoExtension']]")
+        private OnOffSwitch samlServerSignatureKeyInfoExt;
         @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='samlAssertionSignature']]")
         private OnOffSwitch samlAssertionSignature;
         @FindBy(id = "signatureAlgorithm")
@@ -277,6 +280,7 @@ public class ClientSettingsForm extends CreateClientForm {
             if (samlServerSignature.isOn() || samlAssertionSignature.isOn()) {
                 signatureAlgorithm.selectByVisibleText(attributes.get(SAML_SIGNATURE_ALGORITHM));
                 canonicalization.selectByValue("string:" + attributes.get(SAML_SIGNATURE_CANONICALIZATION_METHOD));
+                samlServerSignatureKeyInfoExt.setOn("true".equals(attributes.get(SAML_SERVER_SIGNATURE_KEYINFO_EXT)));
             }
             samlEncrypt.setOn("true".equals(attributes.get(SAML_ENCRYPT)));
             samlClientSignature.setOn("true".equals(attributes.get(SAML_CLIENT_SIGNATURE)));
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index b330a3c..a0b0d78 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -209,6 +209,8 @@ include-authnstatement=Include AuthnStatement
 include-authnstatement.tooltip=Should a statement specifying the method and timestamp be included in login responses?
 sign-documents=Sign Documents
 sign-documents.tooltip=Should SAML documents be signed by the realm?
+sign-documents-redirect-enable-key-info-ext=Optimize REDIRECT signing key lookup
+sign-documents-redirect-enable-key-info-ext.tooltip=When signing SAML documents in REDIRECT binding for SP that is secured by Keycloak adapter, should the ID of the signing key be included in SAML protocol message in <Extensions> element? This optimizes validation of the signature as the validating party uses a single key instead of trying every known key for validation.
 sign-assertions=Sign Assertions
 sign-assertions.tooltip=Should assertions inside SAML documents be signed? This setting isn't needed if document is already being signed.
 signature-algorithm=Signature Algorithm
@@ -506,8 +508,8 @@ force-authentication=Force Authentication
 identity-provider.force-authentication.tooltip=Indicates whether the identity provider must authenticate the presenter directly rather than rely on a previous security context.
 validate-signature=Validate Signature
 saml.validate-signature.tooltip=Enable/disable signature validation of SAML responses.
-validating-x509-certificate=Validating X509 Certificate
-validating-x509-certificate.tooltip=The certificate in PEM format that must be used to check for signatures.
+validating-x509-certificate=Validating X509 Certificates
+validating-x509-certificate.tooltip=The certificate in PEM format that must be used to check for signatures. Multiple certificates can be entered, separated by comma (,).
 saml.import-from-url.tooltip=Import metadata from a remote IDP SAML entity descriptor.
 social.client-id.tooltip=The client identifier registered with the identity provider.
 social.client-secret.tooltip=The client secret registered with the identity provider.
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index 4503b2f..624e9a5 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -860,6 +860,7 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
     $scope.samlAuthnStatement = false;
     $scope.samlMultiValuedRoles = false;
     $scope.samlServerSignature = false;
+    $scope.samlServerSignatureEnableKeyInfoExtension = false;
     $scope.samlAssertionSignature = false;
     $scope.samlClientSignature = false;
     $scope.samlEncrypt = false;
@@ -908,6 +909,13 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
 
             }
         }
+        if ($scope.client.attributes["saml.server.signature.keyinfo.ext"]) {
+            if ($scope.client.attributes["saml.server.signature.keyinfo.ext"] == "true") {
+                $scope.samlServerSignatureEnableKeyInfoExtension = true;
+            } else {
+                $scope.samlServerSignatureEnableKeyInfoExtension = false;
+            }
+        }
         if ($scope.client.attributes["saml.assertion.signature"]) {
             if ($scope.client.attributes["saml.assertion.signature"] == "true") {
                 $scope.samlAssertionSignature = true;
@@ -1115,7 +1123,11 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
             $scope.client.attributes["saml.server.signature"] = "true";
         } else {
             $scope.client.attributes["saml.server.signature"] = "false";
-
+        }
+        if ($scope.samlServerSignatureEnableKeyInfoExtension == true) {
+            $scope.client.attributes["saml.server.signature.keyinfo.ext"] = "true";
+        } else {
+            $scope.client.attributes["saml.server.signature.keyinfo.ext"] = "false";
         }
         if ($scope.samlAssertionSignature == true) {
             $scope.client.attributes["saml.assertion.signature"] = "true";
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
index 8601770..6b0f0a6 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
@@ -118,7 +118,7 @@
                 </div>
             </div>
             <div class="form-group clearfix block" data-ng-show="protocol == 'saml'">
-                <label class="col-md-2 control-label" for="samlServerSignature">{{:: 'include-authnstatement' | translate}}</label>
+                <label class="col-md-2 control-label" for="samlAuthnStatement">{{:: 'include-authnstatement' | translate}}</label>
                 <div class="col-sm-6">
                     <input ng-model="samlAuthnStatement" ng-click="switchChange()" name="samlAuthnStatement" id="samlAuthnStatement" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
                 </div>
@@ -131,6 +131,13 @@
                 </div>
                 <kc-tooltip>{{:: 'sign-documents.tooltip' | translate}}</kc-tooltip>
             </div>
+            <div class="form-group clearfix block" data-ng-show="protocol == 'saml' && samlServerSignature == true">
+                <label class="col-md-2 control-label" for="samlServerSignatureEnableKeyInfoExtension">{{:: 'sign-documents-redirect-enable-key-info-ext' | translate}}</label>
+                <div class="col-sm-6">
+                    <input ng-model="samlServerSignatureEnableKeyInfoExtension" ng-click="switchChange()" name="samlServerSignatureEnableKeyInfoExtension" id="samlServerSignatureEnableKeyInfoExtension" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
+                </div>
+                <kc-tooltip>{{:: 'sign-documents-redirect-enable-key-info-ext.tooltip' | translate}}</kc-tooltip>
+            </div>
             <div class="form-group clearfix block" data-ng-show="protocol == 'saml'">
                 <label class="col-md-2 control-label" for="samlAssertionSignature">{{:: 'sign-assertions' | translate}}</label>
                 <div class="col-sm-6">