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