Fixing UserFederationLdapConnectionTest,LDAPUserLoginTest to work with FIPS (#15299)
closes #14965
This commit is contained in:
parent
2ba5ca3c5f
commit
f616495b05
19 changed files with 164 additions and 29 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -325,7 +325,7 @@ jobs:
|
||||||
declare -A PARAMS TESTGROUP
|
declare -A PARAMS TESTGROUP
|
||||||
PARAMS["bcfips-nonapproved-pkcs12"]="-Pauth-server-quarkus,auth-server-fips140-2"
|
PARAMS["bcfips-nonapproved-pkcs12"]="-Pauth-server-quarkus,auth-server-fips140-2"
|
||||||
# Tests in the package "forms" and some keystore related tests
|
# Tests in the package "forms" and some keystore related tests
|
||||||
TESTGROUP["group1"]="-Dtest=org.keycloak.testsuite.forms.**,ClientAuthSignedJWTTest,CredentialsTest,JavaKeystoreKeyProviderTest,ServerInfoTest"
|
TESTGROUP["group1"]="-Dtest=org.keycloak.testsuite.forms.**,ClientAuthSignedJWTTest,CredentialsTest,JavaKeystoreKeyProviderTest,ServerInfoTest,UserFederationLdapConnectionTest,LDAPUserLoginTest"
|
||||||
|
|
||||||
./mvnw clean install -nsu -B ${PARAMS["${{ matrix.server }}"]} ${TESTGROUP["${{ matrix.tests }}"]} -f testsuite/integration-arquillian/tests/base/pom.xml | misc/log/trimmer.sh
|
./mvnw clean install -nsu -B ${PARAMS["${{ matrix.server }}"]} ${TESTGROUP["${{ matrix.tests }}"]} -f testsuite/integration-arquillian/tests/base/pom.xml | misc/log/trimmer.sh
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,13 @@ import java.security.cert.CertificateException;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.CollectionCertStoreParameters;
|
import java.security.cert.CollectionCertStoreParameters;
|
||||||
import java.security.spec.ECParameterSpec;
|
import java.security.spec.ECParameterSpec;
|
||||||
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.NoSuchPaddingException;
|
import javax.crypto.NoSuchPaddingException;
|
||||||
import javax.crypto.SecretKeyFactory;
|
import javax.crypto.SecretKeyFactory;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
import org.keycloak.common.util.KeystoreUtil.KeystoreFormat;
|
import org.keycloak.common.util.KeystoreUtil.KeystoreFormat;
|
||||||
|
|
||||||
|
@ -113,4 +115,13 @@ public interface CryptoProvider {
|
||||||
|
|
||||||
Signature getSignature(String sigAlgName) throws NoSuchAlgorithmException, NoSuchProviderException;
|
Signature getSignature(String sigAlgName) throws NoSuchAlgorithmException, NoSuchProviderException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap given SSLSocketFactory and decorate it with some additional functionality.
|
||||||
|
*
|
||||||
|
* This method is used in the context of truststore (where Keycloak is SSL client)
|
||||||
|
*
|
||||||
|
* @param delegate The original factory to wrap. Usually default java SSLSocketFactory
|
||||||
|
* @return decorated factory
|
||||||
|
*/
|
||||||
|
SSLSocketFactory wrapFactoryForTruststore(SSLSocketFactory delegate);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.common.util;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
@ -47,6 +48,16 @@ public class UriUtils {
|
||||||
return originPattern.matcher(url).matches();
|
return originPattern.matcher(url).matches();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getHost(String uri) {
|
||||||
|
try {
|
||||||
|
if (uri == null) return null;
|
||||||
|
URI url = new URI(uri);
|
||||||
|
return url.getHost();
|
||||||
|
} catch (URISyntaxException uriSyntaxException) {
|
||||||
|
throw new IllegalArgumentException("URI '" + uri + "' is not valid.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static MultivaluedHashMap<String, String> decodeQueryString(String queryString) {
|
public static MultivaluedHashMap<String, String> decodeQueryString(String queryString) {
|
||||||
MultivaluedHashMap<String, String> map = new MultivaluedHashMap<String, String>();
|
MultivaluedHashMap<String, String> map = new MultivaluedHashMap<String, String>();
|
||||||
if (queryString == null || queryString.equals("")) return map;
|
if (queryString == null || queryString.equals("")) return map;
|
||||||
|
|
|
@ -18,10 +18,12 @@ import java.security.cert.CollectionCertStoreParameters;
|
||||||
import java.security.spec.ECParameterSpec;
|
import java.security.spec.ECParameterSpec;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.NoSuchPaddingException;
|
import javax.crypto.NoSuchPaddingException;
|
||||||
import javax.crypto.SecretKeyFactory;
|
import javax.crypto.SecretKeyFactory;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
import org.bouncycastle.jce.ECNamedCurveTable;
|
import org.bouncycastle.jce.ECNamedCurveTable;
|
||||||
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
|
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
|
||||||
|
@ -176,4 +178,8 @@ public class DefaultCryptoProvider implements CryptoProvider {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SSLSocketFactory wrapFactoryForTruststore(SSLSocketFactory delegate) {
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,10 +34,12 @@ import java.security.spec.ECGenParameterSpec;
|
||||||
import java.security.spec.ECParameterSpec;
|
import java.security.spec.ECParameterSpec;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.NoSuchPaddingException;
|
import javax.crypto.NoSuchPaddingException;
|
||||||
import javax.crypto.SecretKeyFactory;
|
import javax.crypto.SecretKeyFactory;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
import org.keycloak.common.crypto.CertificateUtilsProvider;
|
import org.keycloak.common.crypto.CertificateUtilsProvider;
|
||||||
import org.keycloak.common.crypto.CryptoConstants;
|
import org.keycloak.common.crypto.CryptoConstants;
|
||||||
|
@ -167,4 +169,9 @@ public class WildFlyElytronProvider implements CryptoProvider {
|
||||||
return Signature.getInstance(JavaAlgorithm.getJavaAlgorithm(sigAlgName));
|
return Signature.getInstance(JavaAlgorithm.getJavaAlgorithm(sigAlgName));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SSLSocketFactory wrapFactoryForTruststore(SSLSocketFactory delegate) {
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,10 @@
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-server-spi-private</artifactId>
|
<artifactId>keycloak-server-spi-private</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-services</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
<artifactId>httpclient</artifactId>
|
<artifactId>httpclient</artifactId>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.keycloak.crypto.fips;
|
package org.keycloak.crypto.fips;
|
||||||
|
|
||||||
|
import java.net.Socket;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.KeyFactory;
|
import java.security.KeyFactory;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
|
@ -22,12 +23,17 @@ import java.security.cert.CertificateException;
|
||||||
import java.security.cert.CertificateFactory;
|
import java.security.cert.CertificateFactory;
|
||||||
import java.security.cert.CollectionCertStoreParameters;
|
import java.security.cert.CollectionCertStoreParameters;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.NoSuchPaddingException;
|
import javax.crypto.NoSuchPaddingException;
|
||||||
import javax.crypto.SecretKeyFactory;
|
import javax.crypto.SecretKeyFactory;
|
||||||
|
import javax.net.ssl.SNIHostName;
|
||||||
|
import javax.net.ssl.SSLParameters;
|
||||||
|
import javax.net.ssl.SSLSocket;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
|
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
|
||||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||||
|
@ -35,7 +41,9 @@ import org.bouncycastle.crypto.fips.FipsRSA;
|
||||||
import org.bouncycastle.crypto.fips.FipsSHS;
|
import org.bouncycastle.crypto.fips.FipsSHS;
|
||||||
import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;
|
import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;
|
||||||
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
|
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
|
||||||
|
import org.bouncycastle.jsse.util.CustomSSLSocketFactory;
|
||||||
import org.bouncycastle.math.ec.ECCurve;
|
import org.bouncycastle.math.ec.ECCurve;
|
||||||
|
import org.bouncycastle.util.IPAddress;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.common.crypto.CryptoProvider;
|
import org.keycloak.common.crypto.CryptoProvider;
|
||||||
import org.keycloak.common.crypto.ECDSACryptoProvider;
|
import org.keycloak.common.crypto.ECDSACryptoProvider;
|
||||||
|
@ -45,7 +53,10 @@ import org.keycloak.common.crypto.PemUtilsProvider;
|
||||||
import org.keycloak.common.crypto.UserIdentityExtractorProvider;
|
import org.keycloak.common.crypto.UserIdentityExtractorProvider;
|
||||||
import org.keycloak.common.util.BouncyIntegration;
|
import org.keycloak.common.util.BouncyIntegration;
|
||||||
import org.keycloak.common.util.KeystoreUtil.KeystoreFormat;
|
import org.keycloak.common.util.KeystoreUtil.KeystoreFormat;
|
||||||
|
import org.keycloak.common.util.Resteasy;
|
||||||
import org.keycloak.crypto.JavaAlgorithm;
|
import org.keycloak.crypto.JavaAlgorithm;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -209,4 +220,46 @@ public class FIPS1402Provider implements CryptoProvider {
|
||||||
return Signature.getInstance(JavaAlgorithm.getJavaAlgorithm(sigAlgName), BouncyIntegration.PROVIDER);
|
return Signature.getInstance(JavaAlgorithm.getJavaAlgorithm(sigAlgName), BouncyIntegration.PROVIDER);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SSLSocketFactory wrapFactoryForTruststore(SSLSocketFactory delegate) {
|
||||||
|
KeycloakSession session = Resteasy.getProvider().getContextData(KeycloakSession.class);
|
||||||
|
if (session == null) {
|
||||||
|
log.tracef("Not found keycloakSession in the resteasy context when trying to retrieve hostname attribute from it");
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
String hostname = session.getAttribute(Constants.SSL_SERVER_HOST_ATTR, String.class);
|
||||||
|
log.tracef("Found hostname '%s' to be used by SSLSocketFactory", hostname);
|
||||||
|
if (hostname == null) return delegate;
|
||||||
|
|
||||||
|
// See https://downloads.bouncycastle.org/fips-java/BC-FJA-(D)TLSUserGuide-1.0.9.pdf - Section 3.5.2 (Endpoint identification)
|
||||||
|
return new CustomSSLSocketFactory(delegate) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Socket configureSocket(Socket s) {
|
||||||
|
if (s instanceof SSLSocket) {
|
||||||
|
SSLSocket ssl = (SSLSocket)s;
|
||||||
|
SNIHostName sniHostName = getSNIHostName(hostname);
|
||||||
|
if (sniHostName != null) {
|
||||||
|
SSLParameters sslParameters = new SSLParameters();
|
||||||
|
sslParameters.setServerNames(Collections.singletonList(sniHostName));
|
||||||
|
ssl.setSSLParameters(sslParameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SNIHostName getSNIHostName(String host) {
|
||||||
|
if (!IPAddress.isValid(host)) {
|
||||||
|
try {
|
||||||
|
return new SNIHostName(host);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
log.warnf(e, "Not possible to create SNIHostName from the host '%s'", host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,8 +31,12 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.naming.directory.SearchControls;
|
import javax.naming.directory.SearchControls;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.common.util.UriUtils;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.component.ComponentValidationException;
|
import org.keycloak.component.ComponentValidationException;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.LDAPConstants;
|
import org.keycloak.models.LDAPConstants;
|
||||||
import org.keycloak.models.ModelException;
|
import org.keycloak.models.ModelException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
@ -57,6 +61,8 @@ import org.keycloak.storage.ldap.mappers.membership.MembershipType;
|
||||||
*/
|
*/
|
||||||
public class LDAPUtils {
|
public class LDAPUtils {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(LDAPUtils.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to crate a user in the LDAP. The user will be created when all
|
* Method to crate a user in the LDAP. The user will be created when all
|
||||||
* mandatory attributes specified by the mappers are set. The method
|
* mandatory attributes specified by the mappers are set. The method
|
||||||
|
@ -374,4 +380,10 @@ public class LDAPUtils {
|
||||||
|
|
||||||
return userModelProperties;
|
return userModelProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setLDAPHostnameToKeycloakSession(KeycloakSession session,LDAPConfig ldapConfig) {
|
||||||
|
String hostname = UriUtils.getHost(ldapConfig.getConnectionUrl());
|
||||||
|
session.setAttribute(Constants.SSL_SERVER_HOST_ATTR, hostname);
|
||||||
|
log.tracef("Setting LDAP server hostname '%s' as KeycloakSession attribute", hostname);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.LDAPConstants;
|
import org.keycloak.models.LDAPConstants;
|
||||||
import org.keycloak.storage.ldap.LDAPConfig;
|
import org.keycloak.storage.ldap.LDAPConfig;
|
||||||
|
import org.keycloak.storage.ldap.LDAPUtils;
|
||||||
import org.keycloak.truststore.TruststoreProvider;
|
import org.keycloak.truststore.TruststoreProvider;
|
||||||
import org.keycloak.vault.VaultCharSecret;
|
import org.keycloak.vault.VaultCharSecret;
|
||||||
|
|
||||||
|
@ -66,6 +67,8 @@ public final class LDAPContextManager implements AutoCloseable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createLdapContext() throws NamingException {
|
private void createLdapContext() throws NamingException {
|
||||||
|
LDAPUtils.setLDAPHostnameToKeycloakSession(session, ldapConfig);
|
||||||
|
|
||||||
Hashtable<Object, Object> connProp = getConnectionProperties(ldapConfig);
|
Hashtable<Object, Object> connProp = getConnectionProperties(ldapConfig);
|
||||||
|
|
||||||
if (!LDAPConstants.AUTH_TYPE_NONE.equals(ldapConfig.getAuthType())) {
|
if (!LDAPConstants.AUTH_TYPE_NONE.equals(ldapConfig.getAuthType())) {
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.LDAPConstants;
|
import org.keycloak.models.LDAPConstants;
|
||||||
import org.keycloak.models.ModelException;
|
import org.keycloak.models.ModelException;
|
||||||
import org.keycloak.storage.ldap.LDAPConfig;
|
import org.keycloak.storage.ldap.LDAPConfig;
|
||||||
|
import org.keycloak.storage.ldap.LDAPUtils;
|
||||||
import org.keycloak.storage.ldap.idm.model.LDAPDn;
|
import org.keycloak.storage.ldap.idm.model.LDAPDn;
|
||||||
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
|
import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
|
||||||
import org.keycloak.storage.ldap.idm.store.ldap.extended.PasswordModifyRequest;
|
import org.keycloak.storage.ldap.idm.store.ldap.extended.PasswordModifyRequest;
|
||||||
|
@ -496,6 +497,7 @@ public class LDAPOperationManager {
|
||||||
StartTlsResponse tlsResponse = null;
|
StartTlsResponse tlsResponse = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
LDAPUtils.setLDAPHostnameToKeycloakSession(session, config);
|
||||||
|
|
||||||
Hashtable<Object, Object> env = LDAPContextManager.getNonAuthConnectionProperties(config);
|
Hashtable<Object, Object> env = LDAPContextManager.getNonAuthConnectionProperties(config);
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,8 @@ public final class LdapMapContextManager implements AutoCloseable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createLdapContext() throws NamingException {
|
private void createLdapContext() throws NamingException {
|
||||||
|
LdapMapUtil.setLDAPHostnameToKeycloakSession(session, ldapMapConfig);
|
||||||
|
|
||||||
Hashtable<Object, Object> connProp = getConnectionProperties(ldapMapConfig);
|
Hashtable<Object, Object> connProp = getConnectionProperties(ldapMapConfig);
|
||||||
|
|
||||||
if (!LDAPConstants.AUTH_TYPE_NONE.equals(ldapMapConfig.getAuthType())) {
|
if (!LDAPConstants.AUTH_TYPE_NONE.equals(ldapMapConfig.getAuthType())) {
|
||||||
|
|
|
@ -390,6 +390,7 @@ public class LdapMapOperationManager implements AutoCloseable {
|
||||||
StartTlsResponse tlsResponse = null;
|
StartTlsResponse tlsResponse = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
LdapMapUtil.setLDAPHostnameToKeycloakSession(session, config);
|
||||||
|
|
||||||
Hashtable<Object, Object> env = LdapMapContextManager.getNonAuthConnectionProperties(config);
|
Hashtable<Object, Object> env = LdapMapContextManager.getNonAuthConnectionProperties(config);
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,12 @@
|
||||||
|
|
||||||
package org.keycloak.models.map.storage.ldap.store;
|
package org.keycloak.models.map.storage.ldap.store;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.common.util.UriUtils;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelException;
|
import org.keycloak.models.ModelException;
|
||||||
|
import org.keycloak.models.map.storage.ldap.config.LdapMapConfig;
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
@ -30,6 +35,8 @@ import java.util.TimeZone;
|
||||||
*/
|
*/
|
||||||
public class LdapMapUtil {
|
public class LdapMapUtil {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(LdapMapUtil.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Formats the given date.</p>
|
* <p>Formats the given date.</p>
|
||||||
*
|
*
|
||||||
|
@ -250,5 +257,11 @@ public class LdapMapUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setLDAPHostnameToKeycloakSession(KeycloakSession session, LdapMapConfig ldapConfig) {
|
||||||
|
String hostname = UriUtils.getHost(ldapConfig.getConnectionUrl());
|
||||||
|
session.setAttribute(Constants.SSL_SERVER_HOST_ATTR, hostname);
|
||||||
|
logger.tracef("Setting LDAP server hostname '%s' as KeycloakSession attribute", hostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,4 +146,9 @@ public final class Constants {
|
||||||
|
|
||||||
public static final Boolean REALM_ATTR_USERNAME_CASE_SENSITIVE_DEFAULT = Boolean.FALSE;
|
public static final Boolean REALM_ATTR_USERNAME_CASE_SENSITIVE_DEFAULT = Boolean.FALSE;
|
||||||
public static final String REALM_ATTR_USERNAME_CASE_SENSITIVE = "keycloak.username-search.case-sensitive";
|
public static final String REALM_ATTR_USERNAME_CASE_SENSITIVE = "keycloak.username-search.case-sensitive";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute of KeycloakSession where the hostname of the SSL server can be saved. This can be used by org.keycloak.truststore.SSLSocketFactory
|
||||||
|
*/
|
||||||
|
public static final String SSL_SERVER_HOST_ATTR = "sslServerHost";
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,33 +181,33 @@ public class FileTruststoreProviderFactory implements TruststoreProviderFactory
|
||||||
enumeration = truststore.aliases();
|
enumeration = truststore.aliases();
|
||||||
log.trace("Checking " + truststore.size() + " entries from the truststore.");
|
log.trace("Checking " + truststore.size() + " entries from the truststore.");
|
||||||
while(enumeration.hasMoreElements()) {
|
while(enumeration.hasMoreElements()) {
|
||||||
|
|
||||||
String alias = (String)enumeration.nextElement();
|
String alias = (String)enumeration.nextElement();
|
||||||
Certificate certificate = truststore.getCertificate(alias);
|
readTruststoreEntry(truststore, alias);
|
||||||
|
|
||||||
if (certificate instanceof X509Certificate) {
|
|
||||||
X509Certificate cax509cert = (X509Certificate) certificate;
|
|
||||||
if (isSelfSigned(cax509cert)) {
|
|
||||||
X500Principal principal = cax509cert.getSubjectX500Principal();
|
|
||||||
trustedRootCerts.put(principal, cax509cert);
|
|
||||||
log.debug("Trusted root CA found in trustore : alias : "+alias + " | Subject DN : " + principal);
|
|
||||||
} else {
|
|
||||||
X500Principal principal = cax509cert.getSubjectX500Principal();
|
|
||||||
intermediateCerts.put(principal, cax509cert);
|
|
||||||
log.debug("Intermediate CA found in trustore : alias : "+alias + " | Subject DN : " + principal);
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
log.info("Skipping certificate with alias ["+ alias + "] from truststore, because it's not an X509Certificate");
|
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (KeyStoreException e) {
|
} catch (KeyStoreException e) {
|
||||||
log.error("Error while reading Keycloak truststore "+e.getMessage(),e);
|
log.error("Error while reading Keycloak truststore "+e.getMessage(),e);
|
||||||
} catch (CertificateException e) {
|
}
|
||||||
log.error("Error while reading Keycloak truststore "+e.getMessage(),e);
|
}
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
log.error("Error while reading Keycloak truststore "+e.getMessage(),e);
|
private void readTruststoreEntry(KeyStore truststore, String alias) {
|
||||||
} catch (NoSuchProviderException e) {
|
try {
|
||||||
log.error("Error while reading Keycloak truststore "+e.getMessage(),e);
|
Certificate certificate = truststore.getCertificate(alias);
|
||||||
|
|
||||||
|
if (certificate instanceof X509Certificate) {
|
||||||
|
X509Certificate cax509cert = (X509Certificate) certificate;
|
||||||
|
if (isSelfSigned(cax509cert)) {
|
||||||
|
X500Principal principal = cax509cert.getSubjectX500Principal();
|
||||||
|
trustedRootCerts.put(principal, cax509cert);
|
||||||
|
log.debug("Trusted root CA found in trustore : alias : " + alias + " | Subject DN : " + principal);
|
||||||
|
} else {
|
||||||
|
X500Principal principal = cax509cert.getSubjectX500Principal();
|
||||||
|
intermediateCerts.put(principal, cax509cert);
|
||||||
|
log.debug("Intermediate CA found in trustore : alias : " + alias + " | Subject DN : " + principal);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
log.info("Skipping certificate with alias [" + alias + "] from truststore, because it's not an X509Certificate");
|
||||||
|
} catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | NoSuchProviderException e) {
|
||||||
|
log.warnf("Error while reading Keycloak truststore entry [%s]. Exception message: %s", alias, e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.truststore;
|
package org.keycloak.truststore;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.common.crypto.CryptoIntegration;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
@ -30,9 +31,11 @@ import java.util.Comparator;
|
||||||
* <p>
|
* <p>
|
||||||
* This SSLSocketFactory can only use truststore configured by TruststoreProvider after the ProviderFactory was
|
* This SSLSocketFactory can only use truststore configured by TruststoreProvider after the ProviderFactory was
|
||||||
* initialized using standard Spi load / init mechanism. That will only happen if "truststore" provider is configured
|
* initialized using standard Spi load / init mechanism. That will only happen if "truststore" provider is configured
|
||||||
* in standalone.xml or domain.xml.
|
* by the Keycloak Provider SPI configuration mechanism
|
||||||
* <p>
|
* <p>
|
||||||
* If TruststoreProvider is not available this SSLSocketFactory will delegate all operations to javax.net.ssl.SSLSocketFactory.getDefault().
|
* If TruststoreProvider is not available this SSLSocketFactory will delegate all operations to the SSLSocketFactory
|
||||||
|
* returned by {@link org.keycloak.common.crypto.CryptoProvider#wrapFactoryForTruststore(javax.net.ssl.SSLSocketFactory)},
|
||||||
|
* which will delegate further to the factory returned by javax.net.ssl.SSLSocketFactory.getDefault().
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
|
@ -58,7 +61,7 @@ public class SSLSocketFactory extends javax.net.ssl.SSLSocketFactory implements
|
||||||
sf = (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault();
|
sf = (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
sslsf = sf;
|
sslsf = CryptoIntegration.getProvider().wrapFactoryForTruststore(sf);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized SSLSocketFactory getDefault() {
|
public static synchronized SSLSocketFactory getDefault() {
|
||||||
|
|
Binary file not shown.
|
@ -302,6 +302,7 @@
|
||||||
"file": "${keycloak.truststore.file:target/dependency/keystore/keycloak.truststore}",
|
"file": "${keycloak.truststore.file:target/dependency/keystore/keycloak.truststore}",
|
||||||
"password": "${keycloak.truststore.password:secret}",
|
"password": "${keycloak.truststore.password:secret}",
|
||||||
"hostname-verification-policy": "${keycloak.truststore.policy:WILDCARD}",
|
"hostname-verification-policy": "${keycloak.truststore.policy:WILDCARD}",
|
||||||
|
"type": "${keycloak.truststore.type:}",
|
||||||
"disabled": "${keycloak.truststore.disabled:false}"
|
"disabled": "${keycloak.truststore.disabled:false}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -29,7 +29,6 @@ import org.apache.directory.api.ldap.model.schema.SchemaManager;
|
||||||
import org.apache.directory.server.core.api.DirectoryService;
|
import org.apache.directory.server.core.api.DirectoryService;
|
||||||
import org.apache.directory.server.core.api.interceptor.Interceptor;
|
import org.apache.directory.server.core.api.interceptor.Interceptor;
|
||||||
import org.apache.directory.server.core.api.partition.Partition;
|
import org.apache.directory.server.core.api.partition.Partition;
|
||||||
import org.apache.directory.server.core.factory.AvlPartitionFactory;
|
|
||||||
import org.apache.directory.server.core.factory.DefaultDirectoryServiceFactory;
|
import org.apache.directory.server.core.factory.DefaultDirectoryServiceFactory;
|
||||||
import org.apache.directory.server.core.factory.JdbmPartitionFactory;
|
import org.apache.directory.server.core.factory.JdbmPartitionFactory;
|
||||||
import org.apache.directory.server.core.normalization.NormalizationInterceptor;
|
import org.apache.directory.server.core.normalization.NormalizationInterceptor;
|
||||||
|
@ -45,6 +44,7 @@ import org.keycloak.common.util.StreamUtil;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.security.KeyStore;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -175,7 +175,8 @@ public class LDAPEmbeddedServer {
|
||||||
|
|
||||||
public void init() throws Exception {
|
public void init() throws Exception {
|
||||||
log.info("Creating LDAP Directory Service. Config: baseDN=" + baseDN + ", bindHost=" + bindHost + ", bindPort=" + bindPort +
|
log.info("Creating LDAP Directory Service. Config: baseDN=" + baseDN + ", bindHost=" + bindHost + ", bindPort=" + bindPort +
|
||||||
", ldapSaslPrincipal=" + ldapSaslPrincipal + ", directoryServiceFactory=" + directoryServiceFactory + ", ldif=" + ldifFile);
|
", ldapSaslPrincipal=" + ldapSaslPrincipal + ", directoryServiceFactory=" + directoryServiceFactory + ", ldif=" + ldifFile +
|
||||||
|
", enableSSL=" + enableSSL + ", enableStartTLS: " + enableStartTLS + ", keystoreFile: " + keystoreFile + ", default java keystore type: " + KeyStore.getDefaultType());
|
||||||
|
|
||||||
this.directoryService = createDirectoryService();
|
this.directoryService = createDirectoryService();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue