This commit is contained in:
Bill Burke committed 2016-11-14 15:09:41 -05:00
commit cc0eb47814
692 files changed
+6300 -961

No files matched your search

+5
View File
@@ -63,6 +63,11 @@
<artifactId>keycloak-server-spi</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi-private</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-ldap-federation</artifactId>
@@ -20,6 +20,7 @@ package org.keycloak.authentication.authenticators.browser;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.RoleUtils;
import javax.ws.rs.core.MultivaluedMap;
import java.util.List;
@@ -30,7 +31,6 @@ import static org.keycloak.authentication.authenticators.browser.ConditionalOtpF
import static org.keycloak.authentication.authenticators.browser.ConditionalOtpFormAuthenticator.OtpDecision.SHOW_OTP;
import static org.keycloak.authentication.authenticators.browser.ConditionalOtpFormAuthenticator.OtpDecision.SKIP_OTP;
import static org.keycloak.models.utils.KeycloakModelUtils.getRoleFromString;
import static org.keycloak.models.utils.KeycloakModelUtils.hasRole;
/**
* An {@link OTPFormAuthenticator} that can conditionally require OTP authentication.
@@ -264,6 +264,6 @@ public class ConditionalOtpFormAuthenticator extends OTPFormAuthenticator {
RoleModel role = getRoleFromString(context.getRealm(), roleName);
UserModel user = context.getUser();
return hasRole(user.getRoleMappings(), role);
return RoleUtils.hasRole(user.getRoleMappings(), role);
}
}
@@ -142,7 +142,7 @@ public class ScriptBasedAuthenticator implements Authenticator {
RealmModel realm = context.getRealm();
ScriptingProvider scripting = context.getSession().scripting();
ScriptingProvider scripting = context.getSession().getProvider(ScriptingProvider.class);
//TODO lookup script by scriptId instead of creating it every time
ScriptModel script = scripting.createScript(realm.getId(), ScriptModel.TEXT_JAVASCRIPT, scriptName, scriptCode, scriptDescription);
@@ -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);
}
@@ -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) {
@@ -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");
}
@@ -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) {
@@ -140,7 +140,7 @@ public class PasswordCredentialProvider implements CredentialProvider, Credentia
PasswordHashProvider hash = session.getProvider(PasswordHashProvider.class, policy.getHashAlgorithm());
if (hash == null) {
logger.warnv("Realm PasswordPolicy PasswordHashProvider {0} not found", policy.getHashAlgorithm());
return session.getProvider(PasswordHashProvider.class, HashAlgorithmPasswordPolicyProviderFactory.DEFAULT_VALUE);
return session.getProvider(PasswordHashProvider.class, PasswordPolicy.HASH_ALGORITHM_DEFAULT);
}
return hash;
}
@@ -108,7 +108,8 @@ public class ImportUtils {
}
}
RealmImporter realmManager = session.getContext().getRealmManager();
RealmManager realmManager = new RealmManager(session);
realmManager.setContextPath(session.getContext().getContextPath());
realmManager.importRealm(rep);
if (System.getProperty(ExportImportConfig.ACTION) != null) {
@@ -23,7 +23,6 @@ import org.keycloak.models.RealmModel;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.List;
@@ -24,11 +24,17 @@ import org.keycloak.component.ComponentModel;
import org.keycloak.models.RealmModel;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
/**
@@ -61,8 +67,18 @@ public class JavaKeystoreKeyProvider extends AbstractRsaKeyProvider {
String kid = KeyUtils.createKeyId(keyPair.getPublic());
return new Keys(kid, keyPair, certificate);
} catch (Exception e) {
throw new RuntimeException("Failed to load keys", e);
} catch (KeyStoreException kse) {
throw new RuntimeException("KeyStore error on server. " + kse.getMessage(), kse);
} catch (FileNotFoundException fnfe) {
throw new RuntimeException("File not found on server. " + fnfe.getMessage(), fnfe);
} catch (IOException ioe) {
throw new RuntimeException("IO error on server. " + ioe.getMessage(), ioe);
} catch (NoSuchAlgorithmException nsae) {
throw new RuntimeException("Algorithm not available on server. " + nsae.getMessage(), nsae);
} catch (CertificateException ce) {
throw new RuntimeException("Certificate error on server. " + ce.getMessage(), ce);
} catch (UnrecoverableKeyException uke) {
throw new RuntimeException("Keystore on server can not be recovered. " + uke.getMessage(), uke);
}
}
@@ -27,6 +27,7 @@ import org.keycloak.provider.ConfigurationValidationHelper;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.List;
import org.jboss.logging.Logger;
import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
@@ -34,6 +35,7 @@ import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactory {
private static final Logger logger = Logger.getLogger(JavaKeystoreKeyProviderFactory.class);
public static final String ID = "java-keystore";
@@ -77,7 +79,8 @@ public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactor
new JavaKeystoreKeyProvider(session.getContext().getRealm(), model)
.loadKeys(session.getContext().getRealm(), model);
} catch (Throwable t) {
throw new ComponentValidationException("Failed to load keys", t);
logger.error("Failed to load keys.", t);
throw new ComponentValidationException("Failed to load keys. " + t.getMessage(), t);
}
}
@@ -24,7 +24,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.representations.idm.PartialImportRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.managers.UserManager;
import org.keycloak.models.UserManager;
import java.util.HashMap;
import java.util.List;
@@ -42,8 +42,9 @@ public class RedirectUtils {
}
public static String verifyRedirectUri(UriInfo uriInfo, String redirectUri, RealmModel realm, ClientModel client) {
Set<String> validRedirects = client.getRedirectUris();
return verifyRedirectUri(uriInfo, client.getRootUrl(), redirectUri, realm, validRedirects);
if (client != null)
return verifyRedirectUri(uriInfo, client.getRootUrl(), redirectUri, realm, client.getRedirectUris());
return null;
}
public static Set<String> resolveValidRedirects(UriInfo uriInfo, String rootUrl, Set<String> validRedirects) {
@@ -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);
@@ -110,7 +111,7 @@ public class EntityDescriptorDescriptionConverter implements ClientDescriptionCo
String logoutPost = getLogoutLocation(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
if (logoutPost != null) attributes.put(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE, logoutPost);
String logoutRedirect = getLogoutLocation(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
if (logoutPost != null) attributes.put(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_REDIRECT_ATTRIBUTE, logoutRedirect);
if (logoutRedirect != null) attributes.put(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_REDIRECT_ATTRIBUTE, logoutRedirect);
String assertionConsumerServicePostBinding = CoreConfigUtil.getServiceURL(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
if (assertionConsumerServicePostBinding != null) {
@@ -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() {
@@ -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() {
@@ -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";
@@ -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;
}
@@ -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;
}
}
@@ -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() {
@@ -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
@@ -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
@@ -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");
@@ -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
@@ -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
@@ -110,13 +110,6 @@ public class DefaultKeycloakContext implements KeycloakContext {
this.connection = connection;
}
@Override
public RealmImporter getRealmManager() {
RealmManager manager = new RealmManager(session);
manager.setContextPath(getContextPath());
return manager;
}
@Override
public Locale resolveLocale(UserModel user) {
return LocaleHelper.getLocale(session, realm, user);
@@ -59,7 +59,6 @@ public class DefaultKeycloakSession implements KeycloakSession {
private UserProvider userModel;
private UserStorageManager userStorageManager;
private UserCredentialStoreManager userCredentialStorageManager;
private ScriptingProvider scriptingProvider;
private UserSessionProvider sessionProvider;
private UserFederationManager federationManager;
private UserFederatedStorageProvider userFederatedStorageProvider;
@@ -275,14 +274,4 @@ public class DefaultKeycloakSession implements KeycloakSession {
}
}
}
@Override
public ScriptingProvider scripting() {
if (scriptingProvider == null) {
scriptingProvider = getProvider(ScriptingProvider.class);
}
return scriptingProvider;
}
}
@@ -436,7 +436,11 @@ public class AuthenticationManager {
// refresh the cookies!
createLoginCookie(session, realm, userSession.getUser(), userSession, uriInfo, clientConnection);
if (userSession.getState() != UserSessionModel.State.LOGGED_IN) userSession.setState(UserSessionModel.State.LOGGED_IN);
if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getLoginUsername(), uriInfo, clientConnection);
if (userSession.isRememberMe()) {
createRememberMeCookie(realm, userSession.getLoginUsername(), uriInfo, clientConnection);
} else {
expireRememberMeCookie(realm, uriInfo, clientConnection);
}
// Update userSession note with authTime. But just if flag SSO_AUTH is not set
if (!isSSOAuthentication(clientSession)) {
@@ -27,6 +27,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserManager;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.session.UserSessionPersisterProvider;
@@ -66,7 +66,7 @@ import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.BruteForceProtector;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.UserManager;
import org.keycloak.models.UserManager;
import org.keycloak.services.managers.UserSessionManager;
import org.keycloak.services.resources.AccountService;
import org.keycloak.services.validation.Validation;
@@ -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}" />