diff --git a/common/src/main/java/org/keycloak/common/util/CRLUtils.java b/common/src/main/java/org/keycloak/common/util/CRLUtils.java
deleted file mode 100644
index 4e7e0ae0ba..0000000000
--- a/common/src/main/java/org/keycloak/common/util/CRLUtils.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright 2016 Analytical Graphics, Inc. and/or its affiliates
- * and other contributors as indicated by the @author tags.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.keycloak.common.util;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.security.cert.X509Certificate;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.DEROctetString;
-import org.bouncycastle.asn1.x509.CRLDistPoint;
-import org.bouncycastle.asn1.x509.DistributionPoint;
-import org.bouncycastle.asn1.x509.DistributionPointName;
-import org.bouncycastle.asn1.x509.GeneralName;
-import org.bouncycastle.asn1.x509.GeneralNames;
-
-/**
- * @author Peter Nalyvayko
- * @version $Revision: 1 $
- * @since 10/31/2016
- */
-
-public final class CRLUtils {
-
- static {
- BouncyIntegration.init();
- }
-
- private static final String CRL_DISTRIBUTION_POINTS_OID = "2.5.29.31";
-
- /**
- * Retrieves a list of CRL distribution points from CRLDP v3 certificate extension
- * See CRL validation
- * @param cert
- * @return
- * @throws IOException
- */
- public static List getCRLDistributionPoints(X509Certificate cert) throws IOException {
- byte[] data = cert.getExtensionValue(CRL_DISTRIBUTION_POINTS_OID);
- if (data == null) {
- return Collections.emptyList();
- }
-
- List distributionPointUrls = new LinkedList<>();
- DEROctetString octetString;
- try (ASN1InputStream crldpExtensionInputStream = new ASN1InputStream(new ByteArrayInputStream(data))) {
- octetString = (DEROctetString)crldpExtensionInputStream.readObject();
- }
- byte[] octets = octetString.getOctets();
-
- CRLDistPoint crlDP;
- try (ASN1InputStream crldpInputStream = new ASN1InputStream(new ByteArrayInputStream(octets))) {
- crlDP = CRLDistPoint.getInstance(crldpInputStream.readObject());
- }
-
- for (DistributionPoint dp : crlDP.getDistributionPoints()) {
- DistributionPointName dpn = dp.getDistributionPoint();
- if (dpn != null && dpn.getType() == DistributionPointName.FULL_NAME) {
- GeneralName[] names = GeneralNames.getInstance(dpn.getName()).getNames();
- for (GeneralName gn : names) {
- if (gn.getTagNo() == GeneralName.uniformResourceIdentifier) {
- String url = DERIA5String.getInstance(gn.getName()).getString();
- distributionPointUrls.add(url);
- }
- }
- }
- }
-
- return distributionPointUrls;
- }
-
-}
diff --git a/server-spi-private/src/main/java/org/keycloak/truststore/TruststoreProvider.java b/server-spi-private/src/main/java/org/keycloak/truststore/TruststoreProvider.java
index 00b868a2e1..e5238f109e 100755
--- a/server-spi-private/src/main/java/org/keycloak/truststore/TruststoreProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/truststore/TruststoreProvider.java
@@ -20,6 +20,10 @@ package org.keycloak.truststore;
import org.keycloak.provider.Provider;
import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.Map;
+
+import javax.security.auth.x500.X500Principal;
/**
* @author Marko Strukelj
@@ -29,4 +33,14 @@ public interface TruststoreProvider extends Provider {
HostnameVerificationPolicy getPolicy();
KeyStore getTruststore();
+
+ /**
+ * @return root certificates from the configured truststore as a map where the key is the X500Principal of the corresponding X509Certificate
+ */
+ Map getRootCertificates();
+
+ /**
+ * @return intermediate certificates from the configured truststore as a map where the key is the X500Principal of the corresponding X509Certificate
+ */
+ Map getIntermediateCertificates();
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticator.java
index 66b03b26c2..e8571d34e4 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticator.java
@@ -86,10 +86,11 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
protected static class CertificateValidatorConfigBuilder {
- static CertificateValidator.CertificateValidatorBuilder fromConfig(X509AuthenticatorConfigModel config) throws Exception {
+ static CertificateValidator.CertificateValidatorBuilder fromConfig(KeycloakSession session, X509AuthenticatorConfigModel config) throws Exception {
CertificateValidator.CertificateValidatorBuilder builder = new CertificateValidator.CertificateValidatorBuilder();
return builder
+ .session(session)
.keyUsage()
.parse(config.getKeyUsage())
.extendedKeyUsage()
@@ -105,8 +106,8 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth
}
// The method is purely for purposes of facilitating the unit testing
- public CertificateValidator.CertificateValidatorBuilder certificateValidationParameters(X509AuthenticatorConfigModel config) throws Exception {
- return CertificateValidatorConfigBuilder.fromConfig(config);
+ public CertificateValidator.CertificateValidatorBuilder certificateValidationParameters(KeycloakSession session, X509AuthenticatorConfigModel config) throws Exception {
+ return CertificateValidatorConfigBuilder.fromConfig(session, config);
}
protected static class UserIdentityExtractorBuilder {
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/x509/CertificateValidator.java b/services/src/main/java/org/keycloak/authentication/authenticators/x509/CertificateValidator.java
index 1c16322503..900e4217df 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/x509/CertificateValidator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/x509/CertificateValidator.java
@@ -18,7 +18,8 @@
package org.keycloak.authentication.authenticators.x509;
-import org.keycloak.common.util.CRLUtils;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.utils.CRLUtils;
import org.keycloak.common.util.OCSPUtils;
import org.keycloak.models.Constants;
import org.keycloak.services.ServicesLogger;
@@ -54,7 +55,6 @@ import java.util.Set;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
import org.keycloak.saml.common.exceptions.ProcessingException;
import org.keycloak.saml.processing.core.util.XMLSignatureUtil;
@@ -354,7 +354,7 @@ public class CertificateValidator {
}
}
-
+ KeycloakSession session;
X509Certificate[] _certChain;
int _keyUsageBits;
List _extendedKeyUsage;
@@ -373,7 +373,8 @@ public class CertificateValidator {
boolean cRLDPCheckingEnabled,
CRLLoaderImpl crlLoader,
boolean oCSPCheckingEnabled,
- OCSPChecker ocspChecker) {
+ OCSPChecker ocspChecker,
+ KeycloakSession session) {
_certChain = certChain;
_keyUsageBits = keyUsageBits;
_extendedKeyUsage = extendedKeyUsage;
@@ -382,6 +383,7 @@ public class CertificateValidator {
_crlLoader = crlLoader;
_ocspEnabled = oCSPCheckingEnabled;
this.ocspChecker = ocspChecker;
+ this.session = session;
if (ocspChecker == null)
throw new IllegalArgumentException("ocspChecker");
@@ -497,15 +499,11 @@ public class CertificateValidator {
}
}
- private static void checkRevocationStatusUsingCRL(X509Certificate[] certs, CRLLoaderImpl crLoader) throws GeneralSecurityException {
+ private static void checkRevocationStatusUsingCRL(X509Certificate[] certs, CRLLoaderImpl crLoader, KeycloakSession session) throws GeneralSecurityException {
Collection crlColl = crLoader.getX509CRLs();
if (crlColl != null && crlColl.size() > 0) {
for (X509CRL it : crlColl) {
- if (it.isRevoked(certs[0])) {
- String message = String.format("Certificate has been revoked, certificate's subject: %s", certs[0].getSubjectDN().getName());
- logger.debug(message);
- throw new GeneralSecurityException(message);
- }
+ CRLUtils.check(certs, it, session);
}
}
}
@@ -519,7 +517,7 @@ public class CertificateValidator {
return new ArrayList<>();
}
- private static void checkRevocationStatusUsingCRLDistributionPoints(X509Certificate[] certs) throws GeneralSecurityException {
+ private static void checkRevocationStatusUsingCRLDistributionPoints(X509Certificate[] certs, KeycloakSession session) throws GeneralSecurityException {
List distributionPoints = getCRLDistributionPoints(certs[0]);
if (distributionPoints == null || distributionPoints.size() == 0) {
@@ -527,7 +525,7 @@ public class CertificateValidator {
}
for (String dp : distributionPoints) {
logger.tracef("CRL Distribution point: \"%s\"", dp);
- checkRevocationStatusUsingCRL(certs, new CRLFileLoader(dp));
+ checkRevocationStatusUsingCRL(certs, new CRLFileLoader(dp), session);
}
}
@@ -537,9 +535,9 @@ public class CertificateValidator {
}
if (_crlCheckingEnabled) {
if (!_crldpEnabled) {
- checkRevocationStatusUsingCRL(_certChain, _crlLoader /*"crl.pem"*/);
+ checkRevocationStatusUsingCRL(_certChain, _crlLoader, session);
} else {
- checkRevocationStatusUsingCRLDistributionPoints(_certChain);
+ checkRevocationStatusUsingCRLDistributionPoints(_certChain, session);
}
}
if (_ocspEnabled) {
@@ -556,6 +554,7 @@ public class CertificateValidator {
// instances of CertificateValidator type. The design is an adaption of
// the approach described in http://programmers.stackexchange.com/questions/252067/learning-to-write-dsls-utilities-for-unit-tests-and-am-worried-about-extensablit
+ KeycloakSession session;
int _keyUsageBits;
List _extendedKeyUsage;
boolean _crlCheckingEnabled;
@@ -732,6 +731,11 @@ public class CertificateValidator {
}
}
+ public CertificateValidatorBuilder session(KeycloakSession session) {
+ this.session = session;
+ return this;
+ }
+
public KeyUsageValidationBuilder keyUsage() {
return new KeyUsageValidationBuilder(this);
}
@@ -750,7 +754,7 @@ public class CertificateValidator {
}
return new CertificateValidator(certs, _keyUsageBits, _extendedKeyUsage,
_crlCheckingEnabled, _crldpEnabled, _crlLoader, _ocspEnabled,
- new BouncyCastleOCSPChecker(_responderUri, _responderCert));
+ new BouncyCastleOCSPChecker(_responderUri, _responderCert), session);
}
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/x509/ValidateX509CertificateUsername.java b/services/src/main/java/org/keycloak/authentication/authenticators/x509/ValidateX509CertificateUsername.java
index ba0491730a..c86eafb63f 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/x509/ValidateX509CertificateUsername.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/x509/ValidateX509CertificateUsername.java
@@ -69,7 +69,7 @@ public class ValidateX509CertificateUsername extends AbstractX509ClientCertifica
}
// Validate X509 client certificate
try {
- CertificateValidator.CertificateValidatorBuilder builder = certificateValidationParameters(config);
+ CertificateValidator.CertificateValidatorBuilder builder = certificateValidationParameters(context.getSession(), config);
CertificateValidator validator = builder.build(certs);
validator.checkRevocationStatus()
.validateKeyUsage()
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509ClientCertificateAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509ClientCertificateAuthenticator.java
index c55252fa28..1aaaaf3dcc 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509ClientCertificateAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509ClientCertificateAuthenticator.java
@@ -82,7 +82,7 @@ public class X509ClientCertificateAuthenticator extends AbstractX509ClientCertif
// Validate X509 client certificate
try {
- CertificateValidator.CertificateValidatorBuilder builder = certificateValidationParameters(config);
+ CertificateValidator.CertificateValidatorBuilder builder = certificateValidationParameters(context.getSession(), config);
CertificateValidator validator = builder.build(certs);
validator.checkRevocationStatus()
.validateKeyUsage()
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 287d2131b8..a27a6b3e4b 100644
--- a/services/src/main/java/org/keycloak/services/x509/NginxProxySslClientCertificateLookup.java
+++ b/services/src/main/java/org/keycloak/services/x509/NginxProxySslClientCertificateLookup.java
@@ -3,26 +3,20 @@ package org.keycloak.services.x509;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
import java.security.KeyStore;
-import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
-import java.security.PublicKey;
-import java.security.SignatureException;
import java.security.cert.CertPath;
import java.security.cert.CertPathBuilder;
import java.security.cert.CertPathBuilderException;
import java.security.cert.CertStore;
import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
-import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -253,7 +247,8 @@ public class NginxProxySslClientCertificateLookup extends AbstractClientCertific
if ( provider != null && provider.getTruststore() != null ) {
truststore = provider.getTruststore();
- readTruststore();
+ trustedRootCerts = new HashSet<>(provider.getRootCertificates().values());
+ intermediateCerts = new HashSet<>(provider.getIntermediateCertificates().values());
log.debug("Keycloak truststore loaded for NGINX x509cert-lookup provider.");
isTruststoreLoaded = true;
@@ -263,70 +258,4 @@ public class NginxProxySslClientCertificateLookup extends AbstractClientCertific
return isTruststoreLoaded;
}
- /**
- * Get all certificates from Keycloak Truststore, and classify them in two lists : root CAs and intermediates CAs
- */
- private void readTruststore() {
-
- //Reading truststore aliases & certificates
- Enumeration enumeration;
-
- trustedRootCerts = new HashSet();
- intermediateCerts = new HashSet();
-
- try {
-
- enumeration = truststore.aliases();
- log.trace("Checking " + truststore.size() + " entries from the truststore.");
- while(enumeration.hasMoreElements()) {
-
- String alias = (String)enumeration.nextElement();
- Certificate certificate = truststore.getCertificate(alias);
-
- if (certificate instanceof X509Certificate) {
- X509Certificate cax509cert = (X509Certificate) certificate;
- if (isSelfSigned(cax509cert)) {
- trustedRootCerts.add(cax509cert);
- log.debug("Trusted root CA found in trustore : alias : "+alias + " | Subject DN : " + ((X509Certificate) certificate).getSubjectDN() );
- } else {
- intermediateCerts.add(cax509cert);
- log.debug("Intermediate CA found in trustore : alias : "+alias + " | Subject DN : " + ((X509Certificate) certificate).getSubjectDN() );
- }
- } else
- log.info("Skipping certificate with alias ["+ alias + "] from truststore, because it's not an X509Certificate");
-
- }
- } catch (KeyStoreException 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);
- } catch (NoSuchProviderException e) {
- log.error("Error while reading Keycloak truststore "+e.getMessage(),e);
- }
- }
-
- /**
- * Checks whether given X.509 certificate is self-signed.
- */
- public boolean isSelfSigned(X509Certificate cert)
- throws CertificateException, NoSuchAlgorithmException,
- NoSuchProviderException {
- try {
- // Try to verify certificate signature with its own public key
- PublicKey key = cert.getPublicKey();
- cert.verify(key);
- log.trace("certificate " + cert.getSubjectDN() + " detected as root CA");
- return true;
- } catch (SignatureException sigEx) {
- // Invalid signature --> not self-signed
- log.trace("certificate " + cert.getSubjectDN() + " detected as intermediate CA");
- } catch (InvalidKeyException keyEx) {
- // Invalid key --> not self-signed
- log.trace("certificate " + cert.getSubjectDN() + " detected as intermediate CA");
- }
- return false;
- }
-
}
diff --git a/services/src/main/java/org/keycloak/truststore/FileTruststoreProvider.java b/services/src/main/java/org/keycloak/truststore/FileTruststoreProvider.java
index e8d6f02944..4fa13cb4cb 100755
--- a/services/src/main/java/org/keycloak/truststore/FileTruststoreProvider.java
+++ b/services/src/main/java/org/keycloak/truststore/FileTruststoreProvider.java
@@ -18,6 +18,10 @@
package org.keycloak.truststore;
import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.util.Map;
+
+import javax.security.auth.x500.X500Principal;
/**
* @author Marko Strukelj
@@ -26,10 +30,14 @@ public class FileTruststoreProvider implements TruststoreProvider {
private final HostnameVerificationPolicy policy;
private final KeyStore truststore;
+ private final Map rootCertificates;
+ private final Map intermediateCertificates;
- FileTruststoreProvider(KeyStore truststore, HostnameVerificationPolicy policy) {
+ FileTruststoreProvider(KeyStore truststore, HostnameVerificationPolicy policy, Map rootCertificates, Map intermediateCertificates) {
this.policy = policy;
this.truststore = truststore;
+ this.rootCertificates = rootCertificates;
+ this.intermediateCertificates = intermediateCertificates;
}
@Override
@@ -42,6 +50,16 @@ public class FileTruststoreProvider implements TruststoreProvider {
return truststore;
}
+ @Override
+ public Map getRootCertificates() {
+ return rootCertificates;
+ }
+
+ @Override
+ public Map getIntermediateCertificates() {
+ return intermediateCertificates;
+ }
+
@Override
public void close() {
}
diff --git a/services/src/main/java/org/keycloak/truststore/FileTruststoreProviderFactory.java b/services/src/main/java/org/keycloak/truststore/FileTruststoreProviderFactory.java
index 9a99f113bd..c3af8473e1 100755
--- a/services/src/main/java/org/keycloak/truststore/FileTruststoreProviderFactory.java
+++ b/services/src/main/java/org/keycloak/truststore/FileTruststoreProviderFactory.java
@@ -26,7 +26,22 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.security.InvalidKeyException;
import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+import javax.security.auth.x500.X500Principal;
/**
* @author Marko Strukelj
@@ -85,7 +100,8 @@ public class FileTruststoreProviderFactory implements TruststoreProviderFactory
}
}
- provider = new FileTruststoreProvider(truststore, verificationPolicy);
+ TruststoreCertificatesLoader certsLoader = new TruststoreCertificatesLoader(truststore);
+ provider = new FileTruststoreProvider(truststore, verificationPolicy, certsLoader.trustedRootCerts, certsLoader.intermediateCerts);
TruststoreProviderSingleton.set(provider);
log.debug("File trustore provider initialized: " + new File(storepath).getAbsolutePath());
}
@@ -116,4 +132,82 @@ public class FileTruststoreProviderFactory implements TruststoreProviderFactory
public String getId() {
return "file";
}
+
+
+
+ private class TruststoreCertificatesLoader {
+
+ private Map trustedRootCerts = new HashMap<>();
+ private Map intermediateCerts = new HashMap<>();
+
+
+ public TruststoreCertificatesLoader(KeyStore truststore) {
+ readTruststore(truststore);
+ }
+
+ /**
+ * Get all certificates from Keycloak Truststore, and classify them in two lists : root CAs and intermediates CAs
+ */
+ private void readTruststore(KeyStore truststore) {
+
+ //Reading truststore aliases & certificates
+ Enumeration enumeration;
+
+ try {
+
+ enumeration = truststore.aliases();
+ log.trace("Checking " + truststore.size() + " entries from the truststore.");
+ while(enumeration.hasMoreElements()) {
+
+ String alias = (String)enumeration.nextElement();
+ 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 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);
+ } catch (NoSuchProviderException e) {
+ log.error("Error while reading Keycloak truststore "+e.getMessage(),e);
+ }
+ }
+
+ /**
+ * Checks whether given X.509 certificate is self-signed.
+ */
+ private boolean isSelfSigned(X509Certificate cert)
+ throws CertificateException, NoSuchAlgorithmException,
+ NoSuchProviderException {
+ try {
+ // Try to verify certificate signature with its own public key
+ PublicKey key = cert.getPublicKey();
+ cert.verify(key);
+ log.trace("certificate " + cert.getSubjectDN() + " detected as root CA");
+ return true;
+ } catch (SignatureException sigEx) {
+ // Invalid signature --> not self-signed
+ log.trace("certificate " + cert.getSubjectDN() + " detected as intermediate CA");
+ } catch (InvalidKeyException keyEx) {
+ // Invalid key --> not self-signed
+ log.trace("certificate " + cert.getSubjectDN() + " detected as intermediate CA");
+ }
+ return false;
+ }
+ }
}
diff --git a/services/src/main/java/org/keycloak/utils/CRLUtils.java b/services/src/main/java/org/keycloak/utils/CRLUtils.java
new file mode 100644
index 0000000000..bca202e949
--- /dev/null
+++ b/services/src/main/java/org/keycloak/utils/CRLUtils.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2017 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.utils;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.x509.CRLDistPoint;
+import org.bouncycastle.asn1.x509.DistributionPoint;
+import org.bouncycastle.asn1.x509.DistributionPointName;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.jboss.logging.Logger;
+import org.keycloak.common.util.BouncyIntegration;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.truststore.TruststoreProvider;
+import org.wildfly.security.x500.X500;
+
+/**
+ * @author Peter Nalyvayko
+ * @version $Revision: 1 $
+ * @since 10/31/2016
+ */
+
+public final class CRLUtils {
+
+ private static final Logger log = Logger.getLogger(CRLUtils.class);
+
+
+ static {
+ BouncyIntegration.init();
+ }
+
+ private static final String CRL_DISTRIBUTION_POINTS_OID = "2.5.29.31";
+
+ /**
+ * Retrieves a list of CRL distribution points from CRLDP v3 certificate extension
+ * See CRL validation
+ * @param cert
+ * @return
+ * @throws IOException
+ */
+ public static List getCRLDistributionPoints(X509Certificate cert) throws IOException {
+ byte[] data = cert.getExtensionValue(CRL_DISTRIBUTION_POINTS_OID);
+ if (data == null) {
+ return Collections.emptyList();
+ }
+
+ List distributionPointUrls = new LinkedList<>();
+ DEROctetString octetString;
+ try (ASN1InputStream crldpExtensionInputStream = new ASN1InputStream(new ByteArrayInputStream(data))) {
+ octetString = (DEROctetString)crldpExtensionInputStream.readObject();
+ }
+ byte[] octets = octetString.getOctets();
+
+ CRLDistPoint crlDP;
+ try (ASN1InputStream crldpInputStream = new ASN1InputStream(new ByteArrayInputStream(octets))) {
+ crlDP = CRLDistPoint.getInstance(crldpInputStream.readObject());
+ }
+
+ for (DistributionPoint dp : crlDP.getDistributionPoints()) {
+ DistributionPointName dpn = dp.getDistributionPoint();
+ if (dpn != null && dpn.getType() == DistributionPointName.FULL_NAME) {
+ GeneralName[] names = GeneralNames.getInstance(dpn.getName()).getNames();
+ for (GeneralName gn : names) {
+ if (gn.getTagNo() == GeneralName.uniformResourceIdentifier) {
+ String url = DERIA5String.getInstance(gn.getName()).getString();
+ distributionPointUrls.add(url);
+ }
+ }
+ }
+ }
+
+ return distributionPointUrls;
+ }
+
+
+ /**
+ * Check the signature on CRL and check if 1st certificate from the chain ((The actual certificate from the client)) is valid and not available on CRL.
+ *
+ * @param certs The 1st certificate is the actual certificate of the user. The other certificates represents the certificate chain
+ * @param crl Given CRL
+ * @throws GeneralSecurityException if some error in validation happens. Typically certificate not valid, or CRL signature not valid
+ */
+ public static void check(X509Certificate[] certs, X509CRL crl, KeycloakSession session) throws GeneralSecurityException {
+ if (certs.length < 2) {
+ throw new GeneralSecurityException("Not possible to verify signature on CRL. X509 certificate doesn't have CA chain available on it");
+ }
+
+ X500Principal crlIssuerPrincipal = crl.getIssuerX500Principal();
+ X509Certificate crlSignatureCertificate = null;
+
+ // Try to find the certificate in the CA chain, which was used to sign the CRL
+ for (int i=1 ; i rootCerts = truststoreProvider.getRootCertificates();
+ Map intermediateCerts = truststoreProvider.getIntermediateCertificates();
+
+ X509Certificate crlSignatureCertificate = intermediateCerts.get(crlIssuerPrincipal);
+ if (crlSignatureCertificate == null) {
+ crlSignatureCertificate = rootCerts.get(crlIssuerPrincipal);
+ }
+
+ if (crlSignatureCertificate == null) {
+ throw new GeneralSecurityException("Not available certificate for CRL issuer '" + crlIssuerPrincipal + "' in the truststore, nor in the CA chain");
+ } else {
+ log.tracef("Found CRL issuer certificate with subject '%s' in the truststore. Verifying trust anchor", crlIssuerPrincipal);
+ }
+
+ // Check if CRL issuer has trust anchor with the checked certificate (See https://tools.ietf.org/html/rfc5280#section-6.3.3 , paragraph (f))
+ Set certificateCAPrincipals = Arrays.asList(certs).stream()
+ .map(X509Certificate::getSubjectX500Principal)
+ .collect(Collectors.toSet());
+
+ // Remove the checked certificate itself
+ certificateCAPrincipals.remove(certs[0].getSubjectX500Principal());
+
+ X509Certificate currentCRLAnchorCertificate = crlSignatureCertificate;
+ X500Principal currentCRLAnchorPrincipal = crlIssuerPrincipal;
+ while (true) {
+ if (certificateCAPrincipals.contains(currentCRLAnchorPrincipal)) {
+ log.tracef("Found trust anchor of the CRL issuer '%s' in the CA chain. Anchor is '%s'", crlIssuerPrincipal, currentCRLAnchorPrincipal);
+ break;
+ }
+
+ // Try to see the anchor
+ currentCRLAnchorPrincipal = currentCRLAnchorCertificate.getIssuerX500Principal();
+
+ currentCRLAnchorCertificate = intermediateCerts.get(currentCRLAnchorPrincipal);
+ if (currentCRLAnchorCertificate == null) {
+ currentCRLAnchorCertificate = rootCerts.get(currentCRLAnchorPrincipal);
+ }
+ if (currentCRLAnchorCertificate == null) {
+ throw new GeneralSecurityException("Certificate for CRL issuer '" + crlIssuerPrincipal + "' available in the truststore, but doesn't have trust anchors with the CA chain.");
+ }
+ }
+
+ return crlSignatureCertificate;
+ }
+
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/keystore/ca.crt b/testsuite/integration-arquillian/servers/auth-server/jboss/common/keystore/ca.crt
index d37a76ed8c..bcfaf61a04 100644
--- a/testsuite/integration-arquillian/servers/auth-server/jboss/common/keystore/ca.crt
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/keystore/ca.crt
@@ -1,26 +1,69 @@
-Bag Attributes
- friendlyName: localhost
- localKeyID: 54 69 6D 65 20 31 34 37 37 32 37 36 33 32 32 32 32 35
-subject=/C=US/ST=MA/L=Westword/O=Red Hat/OU=Keycloak/CN=localhost
-issuer=/C=US/ST=MA/L=Westword/O=Red Hat/OU=Keycloak/CN=localhost
-----BEGIN CERTIFICATE-----
-MIIDazCCAlOgAwIBAgIERfv3izANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJV
-UzELMAkGA1UECBMCTUExETAPBgNVBAcTCFdlc3R3b3JkMRAwDgYDVQQKEwdSZWQg
-SGF0MREwDwYDVQQLEwhLZXljbG9hazESMBAGA1UEAxMJbG9jYWxob3N0MB4XDTE1
-MTIwNDA2NTExOFoXDTQ1MTEyNjA2NTExOFowZjELMAkGA1UEBhMCVVMxCzAJBgNV
-BAgTAk1BMREwDwYDVQQHEwhXZXN0d29yZDEQMA4GA1UEChMHUmVkIEhhdDERMA8G
-A1UECxMIS2V5Y2xvYWsxEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcN
-AQEBBQADggEPADCCAQoCggEBAIb7QEw18tpTIVoLUS8kpZaU84btm4nkbVrVNOxC
-zsOVfhFGsc6kUamhHokvvOSWqHS+5FOTVWHPYrNTIwm1vodkqiy7xLCC8MWTrtU5
-RwcrCZ8Mwkm0EUCLCTY113j9egIg+Uj4nkQyTPGNliygf+ef3finzUfarc1lBAHD
-+Z7cjrx4odtvQu88oGdhEXv5GoIno4bwkLRJKWWw9MRZGBxdTJlRGJ2hr0FVtNTw
-sMvgR6ZeDosH8zNNLikLuwMAl7qxCgzppfmZCGKF2H/JLaXUo1oCIwdtCSSJufGJ
-sa9cjdehroVIaiVaASQDKVUStoFz4kYrqUzOves4waJsRvcCAwEAAaMhMB8wHQYD
-VR0OBBYEFFCfEXmWKTtaiZG7tCvBrmQiujrLMA0GCSqGSIb3DQEBCwUAA4IBAQAD
-j/o+snjk/pydFLd3T6gr7k+ZWBi0gQKOOZ+xO9opblYMtG4bRm7wqsTyheUyeTQT
-DZNXIFN4fgCcvHpEi+3M9XL8gySVsu7XzN49UT+KXavwISlbWyryZDH42L/MNCjG
-Z8CD4IsyPAawgrC2Pc8NH8De5YqsGn2DId6R6xjFEumYtAEXXe3Wcp9T4G6yWSXO
-s0rARNfE534Rvne7Gx18g/Lj0BBP7qh3bNeReRmHKpnRK/V90SJNOkpaFF4oAMQr
-0pcZTJa4zoNcAoLHnwNBZmq43cPrffEOOMaCadiSSQ6bsJ0adZ+MSeJ1j4C9SrUn
-M9ES3g9Wj9OcCsHzrTAm
+MIIF9jCCA96gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAlVT
+MQswCQYDVQQIDAJNQTEPMA0GA1UEBwwGQm9zdG9uMRAwDgYDVQQKDAdSZWQgSGF0
+MREwDwYDVQQLDAhLZXljbG9hazEUMBIGA1UEAwwLS2V5Y2xvYWsgQ0ExIzAhBgkq
+hkiG9w0BCQEWFGNvbnRhY3RAa2V5Y2xvYWsub3JnMB4XDTE4MDIyMDE5NTcwMVoX
+DTQ1MDcwODE5NTcwMVowgYcxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJNQTEQMA4G
+A1UECgwHUmVkIEhhdDERMA8GA1UECwwIS2V5Y2xvYWsxITAfBgNVBAMMGEtleWNs
+b2FrIEludGVybWVkaWF0ZSBDQTEjMCEGCSqGSIb3DQEJARYUY29udGFjdEBrZXlj
+bG9hay5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDYix1zJTa6
+TTsmPjctc1R56vYPsIhEeyRis7HL8s+EbFbBpO8jWSSSaJp0MWkahUtWidu9cWK5
+yPC0ezUD3LYclktG1Y6zxeY6G5RnNCUgV8EYkeCJAmlGVhgFjU+7r6HNh1L2sLJe
+jUOKMsKcIxt1TpiUbph/3J1TrqPWDD1jIwB9337dvZfXdwIa45phk1Sb7wgR6aB4
+mJPKBpekkh/5Wh5QRXI+2+Vv1Mhq6Stx1MdE4P2u8lblICOlnCaIWiI6B27yot2x
+hcie1wvFwa1iqtBr4tIHLIn0XNKwqoeooM+WHlkwjMF/Yp1zYJJJmkXjh1a3ZIT5
+7We1U3RxJrLfxE0D4Gm/S7Q302xxiAuDdycHx6oz4qYYwIYZVk+/8q4CDXVyo0aC
+Y4e9fsAPmJvy5TwKZOKocoj+BFAyRwPd1iVrSGeAQTJBPcMgu70o9xVBnU8Pgsif
+O5HzpXw9LTRrDaTS4BZ/rYA9PDLzexMVrgVCg+X1dRd3T9IsLPOlo+HCpfNGhfgR
+lwp8/SRGmBuiaG5k6kaScP5mimSGYOvhjRHLNkY+Rgtl+hrMDn8DFd75PibM95hG
+ia9k1qbrjmj9gRGA4xz1QBqewd2TTgAhaKxDFqQec+cJ15vf5AxB4A/KqFmqYXYX
+AQpKczbt2goTyb2Annhpa5WJe/sYvYqTUwIDAQABo2YwZDAdBgNVHQ4EFgQURxJ8
+iQtHVxlUCvUDBM2fhqKWdpQwHwYDVR0jBBgwFoAUIrj0u3MAxyk/k4Cl9hxSAmrL
+elIwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcN
+AQELBQADggIBAFwmiG2sd77dmX+klIeLVIYq4X3VwNijwzpuilDPMqSfSlBawj8f
+PjwFJYzpcl2pe/Lq6sq96VMkN65/AUs/XZOW+ybgE7ZuJlfT12sk48TPgaVvP2dJ
+5ud2l+DWYaH6KjU3B/xx8xttN73BilMobaJMDy02TLK6VgHPtV3bRyPOQNsGrOmp
+wJMPi7t9UjcMm0THhVHdP881ryGXraNb38x5AgTILUwRYmwjtc1Rrlls0eKLtoAl
+n5oScPDPeZELVunFFJ/ZX2lx5yApWpP1sMyzvJxnZhruuzfxsW60Tp+6Q8rHkabw
+ZnnkHgi53/Gnp3H7l/kszM+hNYJXTDTHdPTQMETHEHqiWOzYttBTM8p/ffb3haTm
+UnPb5fuRXJxX8vMxA1h6nSFWtQEQbvlGiS2oGNAOi5XlTsE+mjYMALuAPID9v8Yx
+3eTyI7a4I+qy3a+0Q1iBFsAM75q6cbne7LK8FjLHDnZvHOnredoR/tmebgphD4C3
+p4xNlwocSs+Fhjqsf6L5AvAc8fLP1206f/lp/9qEnvD0kocw2KvxwZY2yDtf115z
+aHxhil32iWME340LVSYyQZqwPPr3N2t4CGZsgGs8vPXLECAGqrT3V2/I3iZNF3J5
+i0GE63/1Q35BPHxPAJcqB/a5woBwo/Ae40u6qWR15keFp3UaJ0M/C9GR
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+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
+SCc=
-----END CERTIFICATE-----
diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/keystore/intermediate-ca-3.crl b/testsuite/integration-arquillian/servers/auth-server/jboss/common/keystore/intermediate-ca-3.crl
new file mode 100644
index 0000000000..5fadae53f0
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/keystore/intermediate-ca-3.crl
@@ -0,0 +1,13 @@
+-----BEGIN X509 CRL-----
+MIIB3zCByAIBATANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJVUzELMAkGA1UE
+CAwCTUExEDAOBgNVBAoMB1JlZCBIYXQxETAPBgNVBAsMCEtleWNsb2FrMSMwIQYD
+VQQDDBpLZXljbG9hayBJbnRlcm1lZGlhdGUgQ0EgMxcNMTkwMzIxMTgzNTQ3WhcN
+MTkwNDIwMTgzNTQ3WqAwMC4wHwYDVR0jBBgwFoAUGFyPCYy3yxTPqAoIcfLf0fto
+r9MwCwYDVR0UBAQCAhACMA0GCSqGSIb3DQEBCwUAA4IBAQDlowilp+jZ2OtWpwSa
+bE2Cglm/6K8pNAzPROSv+yEc0aFUT+tm6yRWaITy+1hl5cLh2QAJk3E7gtXRhhXc
+mf39U0k/WOSmcZCCUwgJuhudpeweFk7XOL3+Jr0npRTaWP583nZNVZQzFxHBZ5T/
+7H3rzjcuvYo9IzRroTNEaol19EgavrnAmG2jMM4Q/9YdYIBjxFJAigH/uGV51O0G
+EV4PNDm+uCvfYQfa1GsGCszaUH2Ixpl9Jr2MP6LN5/w4dGzdq+yDz1srsfgFzVNj
+cBz3lf1ogFKeRmLF2tTNJ1HLTsOImjvnZqRiD/3EkzLBMsptVFns+/Aq5MRo1pKJ
+iNEi
+-----END X509 CRL-----
diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/keystore/intermediate-ca-invalid-signature.crl b/testsuite/integration-arquillian/servers/auth-server/jboss/common/keystore/intermediate-ca-invalid-signature.crl
new file mode 100644
index 0000000000..7b03c91124
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/keystore/intermediate-ca-invalid-signature.crl
@@ -0,0 +1,17 @@
+-----BEGIN X509 CRL-----
+MIICsjCCAZoCAQEwDQYJKoZIhvcNAQELBQAwgYcxCzAJBgNVBAYTAlVTMQswCQYD
+VQQIDAJNQTEQMA4GA1UECgwHUmVkIEhhdDERMA8GA1UECwwIS2V5Y2xvYWsxITAf
+BgNVBAMMGEtleWNsb2FrIEludGVybWVkaWF0ZSBDQTEjMCEGCSqGSIb3DQEJARYU
+Y29udGFjdEBrZXljbG9hay5vcmcXDTE5MDMyMDE5Mjk1MVoXDTE5MDQxOTE5Mjk1
+MVowFTATAgIQCRcNMTkwMzIwMTkyOTUxWqCBxjCBwzCBtAYDVR0jBIGsMIGpgBRw
+AVgUGev7EtO+WNLtEcYGQYoadaGBjaSBijCBhzELMAkGA1UEBhMCVVMxCzAJBgNV
+BAgMAk1BMRAwDgYDVQQKDAdSZWQgSGF0MREwDwYDVQQLDAhLZXljbG9hazEhMB8G
+A1UEAwwYS2V5Y2xvYWsgSW50ZXJtZWRpYXRlIENBMSMwIQYJKoZIhvcNAQkBFhRj
+b250YWN0QGtleWNsb2FrLm9yZ4IBATAKBgNVHRQEAwIBATANBgkqhkiG9w0BAQsF
+AAOCAQEAmurquuW//sYdX0WrAeBDXY81Y0riz9mYgmrYKXMANE2fpybzReYnI0WC
+4JRzfKfdLYWbcJCxdKpsRyzjCh6YVuelKG4nRUHb32/j9XFwdTW1Kv+20PYsZ5co
+doPRty9F7Bg06yV0TJ6bOeYiUx9sR0rXSNvMRp7/fT8PPOZodnU1rkFN9/7CI4tf
+BxaxjTezklBJGRJ3Vj12kGu9D1NFP5k5qrAT2jZfkRx8LOZIkhgfOaYp+0OPN4Q6
+weWWX5ityl5iJWoovNd3cQzY/GtpouX8AzmiKfOiY/oQIyx6U8V0V2peNA1gZFtd
+aHRBPWv6jH8p4KrqxKl+LtyBVl4JLg==
+-----END X509 CRL-----
diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/keystore/keycloak.truststore b/testsuite/integration-arquillian/servers/auth-server/jboss/common/keystore/keycloak.truststore
index d4618681cb..d81fa2ac36 100644
Binary files a/testsuite/integration-arquillian/servers/auth-server/jboss/common/keystore/keycloak.truststore and b/testsuite/integration-arquillian/servers/auth-server/jboss/common/keystore/keycloak.truststore differ
diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/newcerts/intermediate-ca-3.crt b/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/newcerts/intermediate-ca-3.crt
new file mode 100644
index 0000000000..a555defcfd
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/newcerts/intermediate-ca-3.crt
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE-----
+MIIEsTCCApmgAwIBAgICC7kwDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAlVT
+MQswCQYDVQQIDAJNQTEPMA0GA1UEBwwGQm9zdG9uMRAwDgYDVQQKDAdSZWQgSGF0
+MREwDwYDVQQLDAhLZXljbG9hazEUMBIGA1UEAwwLS2V5Y2xvYWsgQ0ExIzAhBgkq
+hkiG9w0BCQEWFGNvbnRhY3RAa2V5Y2xvYWsub3JnMB4XDTE5MDMyMTE4MTk1MFoX
+DTQ2MDgwNjE4MTk1MFowZDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk1BMRAwDgYD
+VQQKDAdSZWQgSGF0MREwDwYDVQQLDAhLZXljbG9hazEjMCEGA1UEAwwaS2V5Y2xv
+YWsgSW50ZXJtZWRpYXRlIENBIDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQD1afusL2MqMMxg20HTGyVt5nxc9MvpzU9yWhKyper9hh/iEDkH06SZ+vIn
+UWGTeRfGmoA3W+IGF4TzSFO2BjOLz6bowNTqpWONZAK0swMauE23sbxA7XfKxmIt
+5pDkQWAWb2kzLoKEXkbmdlF+O/qcM8i+1U7fpg1NjQI1DHvBVMvul/aGyzP6q/Hj
+NEg+7o3y21T/3gq/Yma9L+awX1wLLxtDlSGjP1Q2C19w87ijUIwFUo7AzTOn03SI
+t+Pfc1cdIsmjUG2lDqJTyo/VuqmfWWBMm6p+Ya/Z4Nipk87nxoW60y4EjRL7vxx9
+vyWiRUhP/2pEE5y9LQ0uzxxA4kIPAgMBAAGjRTBDMB0GA1UdDgQWBBQYXI8JjLfL
+FM+oCghx8t/R+2iv0zASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIB
+hjANBgkqhkiG9w0BAQsFAAOCAgEAB5SrTyWz95GqgG0zrtIwJArMY4EUuISGWEgX
+8GpVyP9xTM1he/vOXqFpAQHtygiq5yhyrrMJuu5/m0Ca5NZM1NaM6unJr7mIPEKK
+BP/85Fue/gBe/Q0Vx1VnmxZhszqQT4hLIfR8anLXgK9w8E6lB4HAXgK+VUzzxXLE
+43oc8/L9swEnvCKsLHKynexNxKRIZD3GPmLCciOz+101Fb6a0Nc8+A+5soiQi8gC
+/K9ygXvxqmHtnrTndZmrtJFOaeXwMDAw7q3j25MkRt4fBPcKX0soBuI6/cTE2veH
+GPYXW4QbVswrhJVD/vVuU0VFR1fLnuw9NgDkGz0qoi20x/Ew3HiVi++9EU9YsPgR
+StcfsCTfth2TLsPmSFSKeGMR3e+Hr6xr6fcahSl6QmKcI60EHw+kKqQ3bBq7QOxi
+zEuQ0SunRyuEUewnhMT9s7sIrZ4M2fyiWH3GVm2971J0/ey3NEYSq/Y9fAO6+9xG
+Wlvaps8X5GLs/XYt9HwZNtdb9F3YMNSFi10WBIKqdpppLT9uuIDSK5S+s5chuych
+y2TZiCBwKBt1lRdqEZ6J4vMj7M5eIdj8Pmvp+tmL1kewuTfKaU+2Bpy6l42sYkyM
+jy7Z0XyMITIBnAf9SmElQPUjq+IIYL6HK/i8mI15AHHIiwm1eaUdbvBKJnQS0++b
+0iwvQA4=
+-----END CERTIFICATE-----
diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/newcerts/intermediate-ca-3.key b/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/newcerts/intermediate-ca-3.key
new file mode 100644
index 0000000000..30de8863c7
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/newcerts/intermediate-ca-3.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA9Wn7rC9jKjDMYNtB0xslbeZ8XPTL6c1PcloSsqXq/YYf4hA5
+B9OkmfryJ1Fhk3kXxpqAN1viBheE80hTtgYzi8+m6MDU6qVjjWQCtLMDGrhNt7G8
+QO13ysZiLeaQ5EFgFm9pMy6ChF5G5nZRfjv6nDPIvtVO36YNTY0CNQx7wVTL7pf2
+hssz+qvx4zRIPu6N8ttU/94Kv2JmvS/msF9cCy8bQ5Uhoz9UNgtfcPO4o1CMBVKO
+wM0zp9N0iLfj33NXHSLJo1BtpQ6iU8qP1bqpn1lgTJuqfmGv2eDYqZPO58aFutMu
+BI0S+78cfb8lokVIT/9qRBOcvS0NLs8cQOJCDwIDAQABAoIBAQDMV7AH4fk3AyTa
+LRa1GbBjvvukRuyXQ624MInLGN3+tTRM/mcOPkqbL9l7pYaSzcxfQPwrnCUqH2FD
+VODm+mjnLEL1IMLokke/Thv2q+uUzwtfPe3bPh91xxOu1oGknU7Nv3yf8kUYxItS
+kAgxDO4SLAgl5eTj0hbXkObalwdgpIIpobuiZUtJYRT1kJmmWJS8TDQirEyh298D
+RdLHFB2wFgYkg9EjATM4tnv66YqPlW9YCkt1IlhqNyn+Ui8vo5shNXq1Q8x3p7he
+ZdCqGL3Ddg9e9tZnOcFPCNQhFVIUV3T5e9SsPVimsJBiOzEzL0eYZ+8KyJogYeOq
+yEPjqu0BAoGBAP4rGb1PE5xOg6WJ/PCXlFnCDpEqkXoZbDOPZn7cUl2OvzlIciTl
+VgWt1HXq50zVfu0FHZx8PdmCj5av4wMmmcWvu7HZGTm4TErJ777M6c9IS/fKyL25
+XBzw+HOcvCwq+pMTxKNZxAlNlTW0k9GwJeajXlzFyTDO5pTMiLrlJEOPAoGBAPcu
+u283/CJMKE5YDgecYgwfaznX+Jk7jkiJpxfWWGYJUE3xueFROeqcwSI3WqRKm48N
+hb9DC6yFtgO3uC7zUZFxOltkkkWSSB1PNmjLQl+y7E/UqrDEehIT/MhKXxUW9z4S
+ADMZTZT0qeTlbBuwx6CAdnCAATRsW9peOziiKNmBAoGBAOLKtqbzHn6EmHdnjyln
+N9p3i+QAZdrbQG8pb72W/m+45exJNoCxmnZqy3+EYWtvvVflDq0JN28UTueYfinb
+ka6RxhtFqnqUdo7tbV2FHsP0sMSkT0brVMQGSMtweX+3werm4rkXahMbBR7syFF8
+qfUIpTSGz6UbmSgA8ahCun8FAoGARLJiOUjP9CBCW3Oxgn/95+ybeloBp2Sb6KEJ
+JWDW9JTGEsOJq4tNk1y5eG717A8oKJvTfhJ+HhaTPXlD4RiSpN9ZHqlW1asQC8VG
+E93ZtosdjhpGzhXs7zVK3cd9oXjegguyroDrxOgyh4ETiKaa9Ip/YEjTDOTIqmnh
+/51hyQECgYBJ3KwFfHlm77betQz9lvfU5uRBHjoIdor59tTXmvQ9ffdQtIuMN84w
+2CbBuXrAD1hT2HRazfUncjI2Fj+pXD5+Hu42I31WxwNTdo/POznYu58MLog9RNGv
+Avn456Fw8UmSkxm+zGnVJND9/0761/gOcabnIIm6KjI6WeACD1wJFA==
+-----END RSA PRIVATE KEY-----
diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/openssl.cnf b/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/openssl.cnf
index b596754b01..91de563cdc 100644
--- a/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/openssl.cnf
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/pki/root/ca/openssl.cnf
@@ -94,7 +94,7 @@ keyUsage = critical, digitalSignature, cRLSign, keyCertSign
[ v3_intermediate_ca ]
# Extensions for a typical intermediate CA (`man x509v3_config`).
subjectKeyIdentifier = hash
-authorityKeyIdentifier = keyid:always,issuer
+#authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
@@ -104,7 +104,7 @@ basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
-authorityKeyIdentifier = keyid,issuer
+#authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection
diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml
index 36830e97c5..2b9b5e62dc 100644
--- a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml
@@ -190,8 +190,7 @@
ca.crt
client.crt
client.key
- intermediate-ca.crl
- empty.crl
+ *.crl
other_client.jks
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/AbstractX509AuthenticationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/AbstractX509AuthenticationTest.java
index 1f7f9f5516..5ff3ff454f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/AbstractX509AuthenticationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/AbstractX509AuthenticationTest.java
@@ -92,6 +92,8 @@ public abstract class AbstractX509AuthenticationTest extends AbstractTestRealmKe
public static final String EMPTY_CRL_PATH = "empty.crl";
public static final String INTERMEDIATE_CA_CRL_PATH = "intermediate-ca.crl";
+ public static final String INTERMEDIATE_CA_INVALID_SIGNATURE_CRL_PATH = "intermediate-ca-invalid-signature.crl";
+ public static final String INTERMEDIATE_CA_3_CRL_PATH = "intermediate-ca-3.crl";
protected final Logger log = Logger.getLogger(this.getClass());
static final String REQUIRED = "REQUIRED";
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/CRLRule.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/CRLRule.java
index 05af98fc4d..a6352dbf48 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/CRLRule.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/CRLRule.java
@@ -58,6 +58,8 @@ public class CRLRule extends ExternalResource {
PathHandler pathHandler = new PathHandler();
pathHandler.addExactPath(AbstractX509AuthenticationTest.EMPTY_CRL_PATH, new CRLHandler(AbstractX509AuthenticationTest.EMPTY_CRL_PATH));
pathHandler.addExactPath(AbstractX509AuthenticationTest.INTERMEDIATE_CA_CRL_PATH, new CRLHandler(AbstractX509AuthenticationTest.INTERMEDIATE_CA_CRL_PATH));
+ pathHandler.addExactPath(AbstractX509AuthenticationTest.INTERMEDIATE_CA_INVALID_SIGNATURE_CRL_PATH, new CRLHandler(AbstractX509AuthenticationTest.INTERMEDIATE_CA_INVALID_SIGNATURE_CRL_PATH));
+ pathHandler.addExactPath(AbstractX509AuthenticationTest.INTERMEDIATE_CA_3_CRL_PATH, new CRLHandler(AbstractX509AuthenticationTest.INTERMEDIATE_CA_3_CRL_PATH));
crlResponder = Undertow.builder().addHttpListener(CRL_RESPONDER_PORT, CRL_RESPONDER_HOST)
.setHandler(
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509BrowserCRLTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509BrowserCRLTest.java
index 50a0b3ddec..ab35aaa6d5 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509BrowserCRLTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/x509/X509BrowserCRLTest.java
@@ -30,6 +30,7 @@ import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.util.ContainerAssume;
import org.keycloak.testsuite.util.PhantomJSBrowser;
+import org.keycloak.testsuite.util.WaitUtils;
import org.openqa.selenium.WebDriver;
import static org.hamcrest.Matchers.containsString;
@@ -121,6 +122,42 @@ public class X509BrowserCRLTest extends AbstractX509AuthenticationTest {
}
+ @Test
+ public void loginFailedWithInvalidSignatureCRL() {
+ X509AuthenticatorConfigModel config =
+ new X509AuthenticatorConfigModel()
+ .setCRLEnabled(true)
+ .setCRLRelativePath(CRLRule.CRL_RESPONDER_ORIGIN + "/" + INTERMEDIATE_CA_INVALID_SIGNATURE_CRL_PATH)
+ .setConfirmationPageAllowed(true)
+ .setMappingSourceType(SUBJECTDN_EMAIL)
+ .setUserIdentityMapperType(USERNAME_EMAIL);
+ AuthenticatorConfigRepresentation cfg = newConfig("x509-browser-config", config.getConfig());
+ String cfgId = createConfig(browserExecution.getId(), cfg);
+ Assert.assertNotNull(cfgId);
+
+ // Verify there is an error message because of invalid CRL signature
+ assertLoginFailedWithExpectedX509Error("Certificate validation's failed.\nSignature length not correct");
+ }
+
+
+ @Test
+ public void loginSuccessWithCRLSignedWithIntermediateCA3FromTruststore() {
+ X509AuthenticatorConfigModel config =
+ new X509AuthenticatorConfigModel()
+ .setCRLEnabled(true)
+ .setCRLRelativePath(CRLRule.CRL_RESPONDER_ORIGIN + "/" + INTERMEDIATE_CA_3_CRL_PATH)
+ .setConfirmationPageAllowed(true)
+ .setMappingSourceType(SUBJECTDN_EMAIL)
+ .setUserIdentityMapperType(USERNAME_EMAIL);
+ AuthenticatorConfigRepresentation cfg = newConfig("x509-browser-config", config.getConfig());
+ String cfgId = createConfig(browserExecution.getId(), cfg);
+ Assert.assertNotNull(cfgId);
+
+ // Verify there is an error message because of invalid CRL signature
+ x509BrowserLogin(config, userId, "test-user@localhost", "test-user@localhost");
+ }
+
+
@Test
public void loginWithMultipleRevocationLists() {
X509AuthenticatorConfigModel config =
@@ -157,13 +194,17 @@ public class X509BrowserCRLTest extends AbstractX509AuthenticationTest {
private void assertLoginFailedDueRevokedCertificate() {
+ assertLoginFailedWithExpectedX509Error("Certificate validation's failed.\nCertificate has been revoked, certificate's subject:");
+ }
+
+ private void assertLoginFailedWithExpectedX509Error(String expectedError) {
loginConfirmationPage.open();
loginPage.assertCurrent();
// Verify there is an error message
Assert.assertNotNull(loginPage.getError());
- Assert.assertThat(loginPage.getError(), containsString("Certificate validation's failed.\nCertificate has been revoked, certificate's subject:"));
+ Assert.assertThat(loginPage.getError(), containsString(expectedError));
// Continue with form based login
loginPage.login("test-user@localhost", "password");
@@ -177,5 +218,4 @@ public class X509BrowserCRLTest extends AbstractX509AuthenticationTest {
.removeDetail(Details.REDIRECT_URI)
.assertEvent();
}
-
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/keystore/keycloak.truststore b/testsuite/integration-arquillian/tests/base/src/test/resources/keystore/keycloak.truststore
index da0f709f5a..67b00f8630 100644
Binary files a/testsuite/integration-arquillian/tests/base/src/test/resources/keystore/keycloak.truststore and b/testsuite/integration-arquillian/tests/base/src/test/resources/keystore/keycloak.truststore differ