diff --git a/services/src/main/java/org/keycloak/services/x509/NginxProxySslClientCertificateLookup.java b/services/src/main/java/org/keycloak/services/x509/NginxProxySslClientCertificateLookup.java index 1be1e8d35c..ec19541ec0 100644 --- a/services/src/main/java/org/keycloak/services/x509/NginxProxySslClientCertificateLookup.java +++ b/services/src/main/java/org/keycloak/services/x509/NginxProxySslClientCertificateLookup.java @@ -3,7 +3,6 @@ package org.keycloak.services.x509; import java.io.UnsupportedEncodingException; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; -import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.cert.CertPath; @@ -19,6 +18,7 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import org.jboss.logging.Logger; @@ -27,10 +27,6 @@ import org.keycloak.http.HttpRequest; import org.keycloak.common.crypto.CryptoIntegration; import org.keycloak.common.util.PemException; import org.keycloak.common.util.PemUtils; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.truststore.TruststoreProvider; -import org.keycloak.truststore.TruststoreProviderFactory; /** * The NGINX Provider extract end user X.509 certificate send during TLS mutual authentication, @@ -62,20 +58,27 @@ public class NginxProxySslClientCertificateLookup extends AbstractClientCertific private static final Logger log = Logger.getLogger(NginxProxySslClientCertificateLookup.class); - private static boolean isTruststoreLoaded = false; - - private static KeyStore truststore = null; - private static Set trustedRootCerts = null; - private static Set intermediateCerts = null; + private final boolean isTruststoreLoaded; + private final Set trustedRootCerts; + private final Set intermediateCerts; - public NginxProxySslClientCertificateLookup(String sslCientCertHttpHeader, + public NginxProxySslClientCertificateLookup(String sslClientCertHttpHeader, String sslCertChainHttpHeaderPrefix, int certificateChainLength, - KeycloakSession kcsession) { - super(sslCientCertHttpHeader, sslCertChainHttpHeaderPrefix, certificateChainLength); + Set intermediateCerts, + Set trustedRootCerts, + boolean isTruststoreLoaded + ) { + super(sslClientCertHttpHeader, sslCertChainHttpHeaderPrefix, certificateChainLength); - if (!loadKeycloakTrustStore(kcsession)) { + Objects.requireNonNull(intermediateCerts,"requireNonNull intermediateCerts"); + Objects.requireNonNull(trustedRootCerts,"requireNonNull trustedRootCerts"); + this.intermediateCerts = intermediateCerts; + this.trustedRootCerts = trustedRootCerts; + this.isTruststoreLoaded = isTruststoreLoaded; + + if (!this.isTruststoreLoaded) { log.warn("Keycloak Truststore is null or empty, but it's required for NGINX x509cert-lookup provider"); log.warn(" see Keycloak documentation here : https://www.keycloak.org/docs/latest/server_installation/index.html#_truststore"); } @@ -126,7 +129,7 @@ public class NginxProxySslClientCertificateLookup extends AbstractClientCertific X509Certificate clientCert = getCertificateFromHttpHeader(httpRequest, sslClientCertHttpHeader); if (clientCert != null) { - log.debugf("End user certificate found : Subject DN=[%s] SerialNumber=[%s]", clientCert.getSubjectDN(), clientCert.getSerialNumber()); + log.debugf("End user certificate found : Subject DN=[%s] SerialNumber=[%s]", clientCert.getSubjectX500Principal(), clientCert.getSerialNumber()); // Rebuilding the end user certificate chain using Keycloak Truststore X509Certificate[] certChain = buildChain(clientCert); @@ -134,9 +137,9 @@ public class NginxProxySslClientCertificateLookup extends AbstractClientCertific log.info("Impossible to rebuild end user cert chain : client certificate authentication will fail." ); chain.add(clientCert); } else { - for (X509Certificate cacert : certChain) { - chain.add(cacert); - log.debugf("Rebuilded user cert chain DN : %s", cacert.getSubjectDN().toString() ); + for (X509Certificate caCert : certChain) { + chain.add(caCert); + log.debugf("Rebuilded user cert chain DN : %s", caCert.getSubjectX500Principal()); } } } @@ -148,25 +151,25 @@ public class NginxProxySslClientCertificateLookup extends AbstractClientCertific * we are rebuilding here the end user certificate chain with Keycloak truststore. *
* Please note that Keycloak truststore must contain root and intermediate CA's certificates. - * @param end_user_auth_cert + * @param endUserAuthCert * @return */ - public X509Certificate[] buildChain(X509Certificate end_user_auth_cert) { + private X509Certificate[] buildChain(X509Certificate endUserAuthCert) { - X509Certificate[] user_cert_chain = null; + X509Certificate[] userCertChain = new X509Certificate[0]; try { // No truststore : no way! - if (isTruststoreLoaded == false) { + if (!isTruststoreLoaded) { log.warn("Keycloak Truststore is null, but it is required !"); log.warn(" see https://www.keycloak.org/docs/latest/server_installation/index.html#_truststore"); - return null; + return userCertChain; } // Create the selector that specifies the starting certificate X509CertSelector selector = new X509CertSelector(); - selector.setCertificate(end_user_auth_cert); + selector.setCertificate(endUserAuthCert); // Create the trust anchors (set of root CA certificates) Set trustAnchors = new HashSet(); @@ -184,9 +187,9 @@ public class NginxProxySslClientCertificateLookup extends AbstractClientCertific pkixParams.setMaxPathLength(certificateChainLength); // Adding the list of intermediate certificates + end user certificate - intermediateCerts.add(end_user_auth_cert); - CollectionCertStoreParameters intermediateCA_userCert = new CollectionCertStoreParameters(intermediateCerts); - CertStore intermediateCertStore = CryptoIntegration.getProvider().getCertStore(intermediateCA_userCert); + intermediateCerts.add(endUserAuthCert); + CollectionCertStoreParameters intermediateCAUserCert = new CollectionCertStoreParameters(intermediateCerts); + CertStore intermediateCertStore = CryptoIntegration.getProvider().getCertStore(intermediateCAUserCert); pkixParams.addCertStore(intermediateCertStore); // Build and verify the certification chain (revocation status excluded) @@ -194,9 +197,9 @@ public class NginxProxySslClientCertificateLookup extends AbstractClientCertific CertPath certPath = certPathBuilder.build(pkixParams).getCertPath(); log.debug("Certification path building OK, and contains " + certPath.getCertificates().size() + " X509 Certificates"); - user_cert_chain = convertCertPathtoX509CertArray( certPath ); + userCertChain = convertCertPathToX509CertArray(certPath); - } catch (NoSuchAlgorithmException e) { + } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchProviderException e) { log.error(e.getLocalizedMessage(),e); } catch (CertPathBuilderException e) { if (log.isEnabled(Level.TRACE)) { @@ -204,64 +207,32 @@ public class NginxProxySslClientCertificateLookup extends AbstractClientCertific } else { log.warn(e.getLocalizedMessage()); } - } catch (InvalidAlgorithmParameterException e) { - log.error(e.getLocalizedMessage(),e); - } catch (NoSuchProviderException e) { - log.error(e.getLocalizedMessage(),e); } finally { if (isTruststoreLoaded) { //Remove end user certificate - intermediateCerts.remove(end_user_auth_cert); + intermediateCerts.remove(endUserAuthCert); } } - return user_cert_chain; + return userCertChain; } - public X509Certificate[] convertCertPathtoX509CertArray( CertPath certPath ) { + private X509Certificate[] convertCertPathToX509CertArray(CertPath certPath ) { - X509Certificate[] x509certchain = null; - - if (certPath != null) { - List trustedX509Chain = new ArrayList(); - for (Certificate certificate : certPath.getCertificates()) { - if (certificate instanceof X509Certificate) { - trustedX509Chain.add((X509Certificate) certificate); - } - } - x509certchain = trustedX509Chain.toArray(new X509Certificate[0]); + X509Certificate[] x509certChain = new X509Certificate[0]; + if (certPath == null){ + return x509certChain; } - return x509certchain; - - } - - /** Loading truststore @ first login - * - * @param kcsession - * @return - */ - public boolean loadKeycloakTrustStore(KeycloakSession kcsession) { - - if (!isTruststoreLoaded) { - log.debug(" Loading Keycloak truststore ..."); - KeycloakSessionFactory factory = kcsession.getKeycloakSessionFactory(); - TruststoreProviderFactory truststoreFactory = (TruststoreProviderFactory) factory.getProviderFactory(TruststoreProvider.class, "file"); - - TruststoreProvider provider = truststoreFactory.create(kcsession); - - if (provider != null && provider.getTruststore() != null) { - truststore = provider.getTruststore(); - trustedRootCerts = new HashSet<>(provider.getRootCertificates().values()); - intermediateCerts = new HashSet<>(provider.getIntermediateCertificates().values()); - log.debug("Keycloak truststore loaded for NGINX x509cert-lookup provider."); - - isTruststoreLoaded = true; + List trustedX509Chain = new ArrayList(); + for (Certificate certificate : certPath.getCertificates()) { + if (certificate instanceof X509Certificate) { + trustedX509Chain.add((X509Certificate) certificate); } } - return isTruststoreLoaded; - } + return trustedX509Chain.toArray(x509certChain); + } } diff --git a/services/src/main/java/org/keycloak/services/x509/NginxProxySslClientCertificateLookupFactory.java b/services/src/main/java/org/keycloak/services/x509/NginxProxySslClientCertificateLookupFactory.java index b2bdd249c9..02b6e86e9f 100644 --- a/services/src/main/java/org/keycloak/services/x509/NginxProxySslClientCertificateLookupFactory.java +++ b/services/src/main/java/org/keycloak/services/x509/NginxProxySslClientCertificateLookupFactory.java @@ -3,6 +3,13 @@ package org.keycloak.services.x509; import org.jboss.logging.Logger; import org.keycloak.Config; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.truststore.TruststoreProvider; +import org.keycloak.truststore.TruststoreProviderFactory; + +import java.security.cert.X509Certificate; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; /** * The factory and the corresponding providers extract a client certificate @@ -15,29 +22,40 @@ import org.keycloak.models.KeycloakSession; public class NginxProxySslClientCertificateLookupFactory extends AbstractClientCertificateFromHttpHeadersLookupFactory { - private final static Logger logger = Logger.getLogger(NginxProxySslClientCertificateLookupFactory.class); + private static final Logger logger = Logger.getLogger(NginxProxySslClientCertificateLookupFactory.class); - private final static String PROVIDER = "nginx"; + private static final String PROVIDER = "nginx"; - protected final static String TRUST_PROXY_VERIFICATION = "trust-proxy-verification"; + protected static final String TRUST_PROXY_VERIFICATION = "trust-proxy-verification"; - protected boolean trustProxyVerification = false; + protected boolean trustProxyVerification; + + private volatile boolean isTruststoreLoaded; + + private Set trustedRootCerts; + + private Set intermediateCerts; @Override public void init(Config.Scope config) { super.init(config); - trustProxyVerification = config.getBoolean(TRUST_PROXY_VERIFICATION, false); + this.trustProxyVerification = config.getBoolean(TRUST_PROXY_VERIFICATION, false); logger.tracev("{0}: ''{1}''", TRUST_PROXY_VERIFICATION, trustProxyVerification); + this.isTruststoreLoaded = false; + this.trustedRootCerts = ConcurrentHashMap.newKeySet(); + this.intermediateCerts = ConcurrentHashMap.newKeySet(); + } @Override public X509ClientCertificateLookup create(KeycloakSession session) { + loadKeycloakTrustStore(session); if (trustProxyVerification) { return new NginxProxyTrustedClientCertificateLookup(sslClientCertHttpHeader, sslChainHttpHeaderPrefix, certificateChainLength); } else { return new NginxProxySslClientCertificateLookup(sslClientCertHttpHeader, - sslChainHttpHeaderPrefix, certificateChainLength, session); + sslChainHttpHeaderPrefix, certificateChainLength, intermediateCerts, trustedRootCerts, isTruststoreLoaded); } } @@ -45,4 +63,33 @@ public class NginxProxySslClientCertificateLookupFactory extends AbstractClientC public String getId() { return PROVIDER; } + + /** Loading truststore @ first login + * + * @param kcSession keycloak session + */ + private void loadKeycloakTrustStore(KeycloakSession kcSession) { + + if (isTruststoreLoaded){ + return; + } + + synchronized (this) { + if (isTruststoreLoaded) { + return; + } + logger.debug(" Loading Keycloak truststore ..."); + KeycloakSessionFactory factory = kcSession.getKeycloakSessionFactory(); + TruststoreProviderFactory truststoreFactory = (TruststoreProviderFactory) factory.getProviderFactory(TruststoreProvider.class, "file"); + TruststoreProvider provider = truststoreFactory.create(kcSession); + + if (provider != null && provider.getTruststore() != null) { + trustedRootCerts.addAll(provider.getRootCertificates().values()); + intermediateCerts.addAll(provider.getIntermediateCertificates().values()); + logger.debug("Keycloak truststore loaded for NGINX x509cert-lookup provider."); + + isTruststoreLoaded = true; + } + } + } }