From f89be1813df74e130841f97d32837905345e05f0 Mon Sep 17 00:00:00 2001 From: rmartinc Date: Wed, 25 Dec 2024 10:30:47 +0100 Subject: [PATCH] Check next update time for CRL in certificate validation Closes #35983 Signed-off-by: rmartinc --- docs/documentation/release_notes/index.adoc | 3 + .../release_notes/topics/26_1_1.adoc | 5 + .../topics/authentication/x509.adoc | 3 + ...actX509ClientCertificateAuthenticator.java | 2 + ...ClientCertificateAuthenticatorFactory.java | 11 +++ .../x509/CertificateValidator.java | 92 ++++++++++++------- .../x509/X509AuthenticatorConfigModel.java | 9 ++ .../java/org/keycloak/utils/CRLUtils.java | 10 +- .../common/keystore/empty-expired.crl | 19 ++++ .../auth-server/common/keystore/empty.crl | 30 +++--- .../common/keystore/intermediate-ca.crl | 30 +++--- .../common/pki/root/ca/intermediate/crlnumber | 2 +- .../pki/root/ca/intermediate/crlnumber.old | 2 +- .../x509/AbstractX509AuthenticationTest.java | 1 + .../testsuite/x509/X509BrowserCRLTest.java | 44 +++++++++ 15 files changed, 190 insertions(+), 73 deletions(-) create mode 100644 docs/documentation/release_notes/topics/26_1_1.adoc create mode 100644 testsuite/integration-arquillian/servers/auth-server/common/keystore/empty-expired.crl diff --git a/docs/documentation/release_notes/index.adoc b/docs/documentation/release_notes/index.adoc index d69ecc3a1a5..ad5f3c7be80 100644 --- a/docs/documentation/release_notes/index.adoc +++ b/docs/documentation/release_notes/index.adoc @@ -16,6 +16,9 @@ include::topics/templates/release-header.adoc[] == {project_name_full} 26.2.0 include::topics/26_2_0.adoc[leveloffset=2] +== {project_name_full} 26.1.1 +include::topics/26_1_1.adoc[leveloffset=2] + == {project_name_full} 26.1.0 include::topics/26_1_0.adoc[leveloffset=2] diff --git a/docs/documentation/release_notes/topics/26_1_1.adoc b/docs/documentation/release_notes/topics/26_1_1.adoc new file mode 100644 index 00000000000..f6c47784b2a --- /dev/null +++ b/docs/documentation/release_notes/topics/26_1_1.adoc @@ -0,0 +1,5 @@ += New option in X.509 authenticator to abort authentication if CRL is outdated + +The X.509 authenticator has a new option `x509-cert-auth-crl-abort-if-non-updated` (*CRL abort if non updated* in the Admin Console) to abort the login if a CRL is configured to validate the certificate and the CRL is not updated in the time specified in the next update field. The new option defaults to `true` in the Admin Console. For more details about the CRL next update field, see link:https://datatracker.ietf.org/doc/html/rfc5280#section-5.1.2.5[RFC5280, Section-5.1.2.5]. + +The value `false` is maintained for compatibility with the previous behavior. Note that existing configurations will not have the new option and will act as if this option was set to `false`, but the Admin Console will add the default value `true` on edit. diff --git a/docs/documentation/server_admin/topics/authentication/x509.adoc b/docs/documentation/server_admin/topics/authentication/x509.adoc index 7ff66fbd68d..f2a4ae0da14 100644 --- a/docs/documentation/server_admin/topics/authentication/x509.adoc +++ b/docs/documentation/server_admin/topics/authentication/x509.adoc @@ -134,6 +134,9 @@ Use CDP to check the certificate revocation status. Most PKI authorities include *CRL file path*:: The path to a file containing a CRL list. The value must be a path to a valid file if the *CRL Checking Enabled* option is enabled. +*CRL abort if non updated*:: +A CRL conforming to link:https://datatracker.ietf.org/doc/html/rfc5280#section-5.1.2.5[RFC5280] contains a next update field that indicates the date by which the next CRL will be issued. When that time is passed, the CRL is considered outdated and it should be refreshed. If this option is `true`, the authentication will fail if the CRL is outdated (recommended). If the option is set to `false`, the outdated CRL is still used to validate the user certificates. + *OCSP Checking Enabled*:: Checks the certificate revocation status by using Online Certificate Status Protocol. 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 4cda4e9a5ef..7f4886f67ff 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 @@ -66,6 +66,7 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth public static final String TIMESTAMP_VALIDATION = "x509-cert-auth.timestamp-validation-enabled"; public static final String SERIALNUMBER_HEX = "x509-cert-auth.serialnumber-hex-enabled"; public static final String CRL_RELATIVE_PATH = "x509-cert-auth.crl-relative-path"; + public static final String CRL_ABORT_IF_NON_UPDATED = "x509-cert-auth-crl-abort-if-non-updated"; public static final String OCSPRESPONDER_URI = "x509-cert-auth.ocsp-responder-uri"; public static final String OCSPRESPONDER_CERTIFICATE = "x509-cert-auth.ocsp-responder-certificate"; public static final String MAPPING_SOURCE_SELECTION = "x509-cert-auth.mapping-source-selection"; @@ -115,6 +116,7 @@ public abstract class AbstractX509ClientCertificateAuthenticator implements Auth .mode(config.getCertificatePolicyMode().getMode()) .parse(config.getCertificatePolicy()) .revocation() + .crlAbortIfNonUpdated(config.getCrlAbortIfNonUpdated()) .cRLEnabled(config.getCRLEnabled()) .cRLDPEnabled(config.getCRLDistributionPointEnabled()) .cRLrelativePath(config.getCRLRelativePath()) diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticatorFactory.java index 8b38faff4f0..9828f9c609d 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticatorFactory.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/x509/AbstractX509ClientCertificateAuthenticatorFactory.java @@ -173,6 +173,16 @@ public abstract class AbstractX509ClientCertificateAuthenticatorFactory implemen "Multiple CRLs can be included, however it can affect performance as the certificate will be checked against all listed CRLs." ); + ProviderConfigProperty cRLAbortIfNonUpdated = new ProviderConfigProperty(); + cRLAbortIfNonUpdated.setType(BOOLEAN_TYPE); + cRLAbortIfNonUpdated.setName(CRL_ABORT_IF_NON_UPDATED); + cRLAbortIfNonUpdated.setDefaultValue(Boolean.TRUE.toString()); + cRLAbortIfNonUpdated.setLabel("CRL abort if non updated"); + cRLAbortIfNonUpdated.setHelpText("A CRL conforming RFC 5280 must include a next update time that marks when the CRL will be updated. " + + "If this option is true, the authentication fails when the CRL is outdated and cannot be updated. " + + "If false, the authentication continues when the CRL cannot be updated after the next update time." + ); + ProviderConfigProperty oCspCheckingEnabled = new ProviderConfigProperty(); oCspCheckingEnabled.setType(BOOLEAN_TYPE); oCspCheckingEnabled.setName(ENABLE_OCSP); @@ -248,6 +258,7 @@ public abstract class AbstractX509ClientCertificateAuthenticatorFactory implemen crlCheckingEnabled, crlDPEnabled, cRLRelativePath, + cRLAbortIfNonUpdated, oCspCheckingEnabled, ocspFailOpen, ocspResponderUri, 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 31f3db0c7db..f99f79af9ca 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 @@ -48,6 +48,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Date; import java.util.HashSet; import java.util.Hashtable; import java.util.LinkedList; @@ -238,10 +239,10 @@ public class CertificateValidator { private final List delegates; - public CRLListLoader(KeycloakSession session, String cRLConfigValue) { + public CRLListLoader(KeycloakSession session, String cRLConfigValue, boolean abortIfNonUpdated) { String[] delegatePaths = Constants.CFG_DELIMITER_PATTERN.split(cRLConfigValue); this.delegates = Arrays.stream(delegatePaths) - .map(cRLPath -> new CRLFileLoader(session, cRLPath)) + .map(cRLPath -> new CRLFileLoader(session, cRLPath, abortIfNonUpdated)) .collect(Collectors.toList()); } @@ -261,16 +262,16 @@ public class CertificateValidator { private final KeycloakSession session; private final String cRLPath; private final LdapContext ldapContext; + private final boolean abortIfNonUpdated; - public CRLFileLoader(KeycloakSession session, String cRLPath) { - this.session = session; - this.cRLPath = cRLPath; - ldapContext = new LdapContext(); + public CRLFileLoader(KeycloakSession session, String cRLPath, boolean abortIfNonUpdated) { + this(session, cRLPath, abortIfNonUpdated, new LdapContext()); } - public CRLFileLoader(KeycloakSession session, String cRLPath, LdapContext ldapContext) { + public CRLFileLoader(KeycloakSession session, String cRLPath, boolean abortIfNonUpdated, LdapContext ldapContext) { this.session = session; this.cRLPath = cRLPath; + this.abortIfNonUpdated = abortIfNonUpdated; this.ldapContext = ldapContext; if (ldapContext == null) @@ -278,37 +279,49 @@ public class CertificateValidator { } public Collection getX509CRLs() throws GeneralSecurityException { - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - Collection crlColl = null; + X509CRL crl = loadCRL(); + if (crl == null) { + throw new GeneralSecurityException(String.format("Unable to load CRL from \"%s\"", cRLPath)); + } + if (crl.getNextUpdate() != null && crl.getNextUpdate().compareTo(new Date(Time.currentTimeMillis())) < 0) { + final String message = String.format("CRL from '%s' is not refreshed. Next update is %s.", cRLPath, crl.getNextUpdate()); + logger.warn(message); + if (abortIfNonUpdated) { + throw new GeneralSecurityException(message); + } + } + + return Collections.singletonList(crl); + } + + private X509CRL loadCRL() throws GeneralSecurityException { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509CRL crl = null; if (cRLPath != null) { if (cRLPath.startsWith("http") || cRLPath.startsWith("https")) { // load CRL using remote URI try { - crlColl = loadFromURI(cf, new URI(cRLPath)); + crl = loadFromURI(cf, new URI(cRLPath)); } catch (URISyntaxException e) { logger.error(e.getMessage()); } } else if (cRLPath.startsWith("ldap")) { // load CRL from LDAP try { - crlColl = loadCRLFromLDAP(cf, new URI(cRLPath)); + crl = loadCRLFromLDAP(cf, new URI(cRLPath)); } catch(URISyntaxException e) { logger.error(e.getMessage()); } } else { // load CRL from file - crlColl = loadCRLFromFile(cf, cRLPath); + crl = loadCRLFromFile(cf, cRLPath); } } - if (crlColl == null || crlColl.size() == 0) { - String message = String.format("Unable to load CRL from \"%s\"", cRLPath); - throw new GeneralSecurityException(message); - } - return crlColl; + return crl; } - private Collection loadFromURI(CertificateFactory cf, URI remoteURI) throws GeneralSecurityException { + private X509CRL loadFromURI(CertificateFactory cf, URI remoteURI) throws GeneralSecurityException { try { logger.debugf("Loading CRL from %s", remoteURI.toString()); @@ -320,7 +333,7 @@ public class CertificateValidator { try { InputStream content = response.getEntity().getContent(); X509CRL crl = loadFromStream(cf, content); - return Collections.singleton(crl); + return crl; } finally { EntityUtils.consumeQuietly(response.getEntity()); } @@ -329,11 +342,11 @@ public class CertificateValidator { catch(IOException ex) { logger.errorf(ex.getMessage()); } - return Collections.emptyList(); + return null; } - private Collection loadCRLFromLDAP(CertificateFactory cf, URI remoteURI) throws GeneralSecurityException { + private X509CRL loadCRLFromLDAP(CertificateFactory cf, URI remoteURI) throws GeneralSecurityException { Hashtable env = new Hashtable<>(2); env.put(Context.INITIAL_CONTEXT_FACTORY, ldapContext.getLdapFactoryClassName()); env.put(Context.PROVIDER_URL, remoteURI.toString()); @@ -348,7 +361,7 @@ public class CertificateValidator { throw new CertificateException(String.format("Failed to download CRL from \"%s\"", remoteURI.toString())); } X509CRL crl = loadFromStream(cf, new ByteArrayInputStream(data)); - return Collections.singleton(crl); + return crl; } finally { ctx.close(); } @@ -358,10 +371,10 @@ public class CertificateValidator { logger.error(e.getMessage()); } - return Collections.emptyList(); + return null; } - private Collection loadCRLFromFile(CertificateFactory cf, String relativePath) throws GeneralSecurityException { + private X509CRL loadCRLFromFile(CertificateFactory cf, String relativePath) throws GeneralSecurityException { try { String configDir = System.getProperty("jboss.server.config.dir"); if (configDir != null) { @@ -374,7 +387,7 @@ public class CertificateValidator { } try (FileInputStream is = new FileInputStream(f.getAbsolutePath())) { X509CRL crl = loadFromStream(cf, is); - return Collections.singleton(crl); + return crl; } } } @@ -382,7 +395,7 @@ public class CertificateValidator { catch(IOException ex) { logger.errorf(ex.getMessage()); } - return Collections.emptyList(); + return null; } private X509CRL loadFromStream(CertificateFactory cf, InputStream is) throws IOException, CRLException { DataInputStream dis = new DataInputStream(is); @@ -399,6 +412,7 @@ public class CertificateValidator { List _certificatePolicy; String _certificatePolicyMode; boolean _crlCheckingEnabled; + boolean _crlAbortIfNonUpdated; boolean _crldpEnabled; CRLLoaderImpl _crlLoader; boolean _ocspEnabled; @@ -414,6 +428,7 @@ public class CertificateValidator { int keyUsageBits, List extendedKeyUsage, List certificatePolicy, String certificatePolicyMode, boolean cRLCheckingEnabled, + boolean cRLAbortIfNonUpdated, boolean cRLDPCheckingEnabled, CRLLoaderImpl crlLoader, boolean oCSPCheckingEnabled, @@ -428,6 +443,7 @@ public class CertificateValidator { _certificatePolicy = certificatePolicy; _certificatePolicyMode = certificatePolicyMode; _crlCheckingEnabled = cRLCheckingEnabled; + _crlAbortIfNonUpdated = cRLAbortIfNonUpdated; _crldpEnabled = cRLDPCheckingEnabled; _crlLoader = crlLoader; _ocspEnabled = oCSPCheckingEnabled; @@ -510,7 +526,7 @@ public class CertificateValidator { private static void validatePolicy(X509Certificate[] certs, List expectedPolicies, String policyCheckMode) throws GeneralSecurityException { - if (expectedPolicies == null || expectedPolicies.size() == 0) { + if (expectedPolicies == null || expectedPolicies.isEmpty()) { logger.debug("Certificate Policy validation is not enabled."); return; } @@ -748,7 +764,7 @@ public class CertificateValidator { private static void checkRevocationStatusUsingCRL(X509Certificate[] certs, CRLLoaderImpl crLoader, KeycloakSession session) throws GeneralSecurityException { Collection crlColl = crLoader.getX509CRLs(); - if (crlColl != null && crlColl.size() > 0) { + if (crlColl != null && !crlColl.isEmpty()) { for (X509CRL it : crlColl) { CRLUtils.check(certs, it, session); } @@ -765,15 +781,15 @@ public class CertificateValidator { return new ArrayList<>(); } - private static void checkRevocationStatusUsingCRLDistributionPoints(X509Certificate[] certs, KeycloakSession session) throws GeneralSecurityException { + private static void checkRevocationStatusUsingCRLDistributionPoints(X509Certificate[] certs, KeycloakSession session, boolean abortIfNonUpdated) throws GeneralSecurityException { List distributionPoints = getCRLDistributionPoints(certs[0]); - if (distributionPoints == null || distributionPoints.size() == 0) { + if (distributionPoints == null || distributionPoints.isEmpty()) { throw new GeneralSecurityException("Could not find any CRL distribution points in the certificate, unable to check the certificate revocation status using CRL/DP."); } for (String dp : distributionPoints) { logger.tracef("CRL Distribution point: \"%s\"", dp); - checkRevocationStatusUsingCRL(certs, new CRLFileLoader(session, dp), session); + checkRevocationStatusUsingCRL(certs, new CRLFileLoader(session, dp, abortIfNonUpdated), session); } } @@ -785,7 +801,7 @@ public class CertificateValidator { if (!_crldpEnabled) { checkRevocationStatusUsingCRL(_certChain, _crlLoader, session); } else { - checkRevocationStatusUsingCRLDistributionPoints(_certChain, session); + checkRevocationStatusUsingCRLDistributionPoints(_certChain, session, _crlAbortIfNonUpdated); } } if (_ocspEnabled) { @@ -808,6 +824,7 @@ public class CertificateValidator { List _certificatePolicy; String _certificatePolicyMode; boolean _crlCheckingEnabled; + boolean _crlAbortIfNonUpdated; boolean _crldpEnabled; CRLLoaderImpl _crlLoader; boolean _ocspEnabled; @@ -954,6 +971,11 @@ public class CertificateValidator { _parent = parent; } + public RevocationStatusCheckBuilder crlAbortIfNonUpdated(boolean abortIfNonUpdated) { + _crlAbortIfNonUpdated = abortIfNonUpdated; + return this; + } + public GotCRL cRLEnabled(boolean value) { _crlCheckingEnabled = value; return new GotCRL(); @@ -975,7 +997,7 @@ public class CertificateValidator { public class GotCRLDP { public GotCRLRelativePath cRLrelativePath(String value) { if (value != null) - _crlLoader = new CRLListLoader(session, value); + _crlLoader = new CRLListLoader(session, value, _crlAbortIfNonUpdated); return new GotCRLRelativePath(); } @@ -1071,11 +1093,11 @@ public class CertificateValidator { public CertificateValidator build(X509Certificate[] certs) { if (_crlLoader == null) { - _crlLoader = new CRLFileLoader(session, ""); + _crlLoader = new CRLFileLoader(session, "", _crlAbortIfNonUpdated); } return new CertificateValidator(certs, _keyUsageBits, _extendedKeyUsage, _certificatePolicy, _certificatePolicyMode, - _crlCheckingEnabled, _crldpEnabled, _crlLoader, _ocspEnabled, _ocspFailOpen, + _crlCheckingEnabled, _crlAbortIfNonUpdated, _crldpEnabled, _crlLoader, _ocspEnabled, _ocspFailOpen, new BouncyCastleOCSPChecker(session, _responderUri, _responderCert), session, _timestampValidationEnabled, _trustValidationEnabled); } } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509AuthenticatorConfigModel.java b/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509AuthenticatorConfigModel.java index 0a087e5c59d..7fe0106a999 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509AuthenticatorConfigModel.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/x509/X509AuthenticatorConfigModel.java @@ -161,6 +161,15 @@ public class X509AuthenticatorConfigModel extends AuthenticatorConfigModel { return this; } + public boolean getCrlAbortIfNonUpdated() { + return Boolean.parseBoolean(getConfig().get(CRL_ABORT_IF_NON_UPDATED)); + } + + public X509AuthenticatorConfigModel setCrlAbortIfNonUpdated(boolean crlAbortIfNonUpdated) { + getConfig().put(CRL_ABORT_IF_NON_UPDATED, Boolean.toString(crlAbortIfNonUpdated)); + return this; + } + public String getOCSPResponder() { return getConfig().getOrDefault(OCSPRESPONDER_URI, null); } diff --git a/services/src/main/java/org/keycloak/utils/CRLUtils.java b/services/src/main/java/org/keycloak/utils/CRLUtils.java index 1e8995dd689..466b11df08f 100644 --- a/services/src/main/java/org/keycloak/utils/CRLUtils.java +++ b/services/src/main/java/org/keycloak/utils/CRLUtils.java @@ -31,7 +31,6 @@ import java.util.stream.Collectors; import javax.security.auth.x500.X500Principal; import org.jboss.logging.Logger; -import org.keycloak.common.util.BouncyIntegration; import org.keycloak.models.KeycloakSession; import org.keycloak.truststore.TruststoreProvider; @@ -74,13 +73,12 @@ public final class CRLUtils { // Try to find the CRL issuer certificate in the truststore if (crlSignatureCertificate == null) { log.tracef("Not found CRL issuer '%s' in the CA chain of the certificate. Fallback to lookup CRL issuer in the truststore", crlIssuerPrincipal); - crlSignatureCertificate = findCRLSignatureCertificateInTruststore(session, certs, crl); + findCRLSignatureCertificateInTruststore(session, certs, crl); + } else { + // Verify signature on CRL with the previous found certificate + crl.verify(crlSignatureCertificate.getPublicKey()); } - // Verify signature on CRL - // TODO: It will be nice to cache CRLs and also verify their signatures just once at the time when CRL is loaded, rather than in every request - crl.verify(crlSignatureCertificate.getPublicKey()); - // Finally check if if (crl.isRevoked(certs[0])) { String message = String.format("Certificate has been revoked, certificate's subject: %s", certs[0].getSubjectDN().getName()); diff --git a/testsuite/integration-arquillian/servers/auth-server/common/keystore/empty-expired.crl b/testsuite/integration-arquillian/servers/auth-server/common/keystore/empty-expired.crl new file mode 100644 index 00000000000..ee31d3a8c83 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/common/keystore/empty-expired.crl @@ -0,0 +1,19 @@ +-----BEGIN X509 CRL----- +MIIDAzCB7AIBATANBgkqhkiG9w0BAQsFADCBhzELMAkGA1UEBhMCVVMxCzAJBgNV +BAgMAk1BMRAwDgYDVQQKDAdSZWQgSGF0MREwDwYDVQQLDAhLZXljbG9hazEhMB8G +A1UEAwwYS2V5Y2xvYWsgSW50ZXJtZWRpYXRlIENBMSMwIQYJKoZIhvcNAQkBFhRj +b250YWN0QGtleWNsb2FrLm9yZxcNMTkwMzE0MTEwNTI1WhcNMTkwNDEzMTEwNTI1 +WqAwMC4wHwYDVR0jBBgwFoAURxJ8iQtHVxlUCvUDBM2fhqKWdpQwCwYDVR0UBAQC +AhAAMA0GCSqGSIb3DQEBCwUAA4ICAQAnH3w3I/EYB51RrB0ZEmiVtxbuEoy4bbhj +N1jvXsnM6OZb0me3Q6NzkozgVfDbDrWSGvdOaGqWcWrxsFuCqi5T8yHhTU3eRphh ++z01jOvk3UjfOdEOWMJGJWgBLAQj0RUsxRXqGT6n7gyndB+5RTuN/WNjCd96Vf0q +yRDfoGGlanGh8sg7BFFnTfDH0dYw8ApYbt/rFT1suFmEaiH44Nd8dldSJxqjK2Ph +B0+4etNpZZEjuUGq0U+oYUeppI4oE3vqAPrP9f/Wd0Kc+Ci56RC8X2lf9TSN4qb9 +xv2a85pUgxBhioZT2fXTDjiUbYKXZhj5tMPixP0M+NcI2yutHpm0Y44BO76bmcVf +8AAfH8RcyJlPwfEGwENzY+CZTqCYbCbSg12CLs9UL6ITx8Z1lt/LnLOGCq672rWb +Pw3wO3Wmam8Zdyyr2Pff+/+ZcL587/Xif+ho48JSWrJynGwOe9WEBjWqYkfcxCP7 +plZxWj8MF7A389eBmpLF95BVLFHJECJYUfN7CPU2g+4cKSuzXenlSWtqDs/GdbeJ +tFMoA9SzFut9tZg7BMA/gXF/2dTMsjU/1Cy1AVzvWl654wB6OYZv8pktwrjQ77qj +S0X4hovoKfpoxYIzw1OTdOuyu7oc6Wh3XWtJWHssrdZuGczDTXtu1qRstFeag/iO +cpvmIaTdvQ== +-----END X509 CRL----- diff --git a/testsuite/integration-arquillian/servers/auth-server/common/keystore/empty.crl b/testsuite/integration-arquillian/servers/auth-server/common/keystore/empty.crl index ee31d3a8c83..54f1db96382 100644 --- a/testsuite/integration-arquillian/servers/auth-server/common/keystore/empty.crl +++ b/testsuite/integration-arquillian/servers/auth-server/common/keystore/empty.crl @@ -1,19 +1,19 @@ -----BEGIN X509 CRL----- -MIIDAzCB7AIBATANBgkqhkiG9w0BAQsFADCBhzELMAkGA1UEBhMCVVMxCzAJBgNV +MIIDBTCB7gIBATANBgkqhkiG9w0BAQsFADCBhzELMAkGA1UEBhMCVVMxCzAJBgNV BAgMAk1BMRAwDgYDVQQKDAdSZWQgSGF0MREwDwYDVQQLDAhLZXljbG9hazEhMB8G A1UEAwwYS2V5Y2xvYWsgSW50ZXJtZWRpYXRlIENBMSMwIQYJKoZIhvcNAQkBFhRj -b250YWN0QGtleWNsb2FrLm9yZxcNMTkwMzE0MTEwNTI1WhcNMTkwNDEzMTEwNTI1 -WqAwMC4wHwYDVR0jBBgwFoAURxJ8iQtHVxlUCvUDBM2fhqKWdpQwCwYDVR0UBAQC -AhAAMA0GCSqGSIb3DQEBCwUAA4ICAQAnH3w3I/EYB51RrB0ZEmiVtxbuEoy4bbhj -N1jvXsnM6OZb0me3Q6NzkozgVfDbDrWSGvdOaGqWcWrxsFuCqi5T8yHhTU3eRphh -+z01jOvk3UjfOdEOWMJGJWgBLAQj0RUsxRXqGT6n7gyndB+5RTuN/WNjCd96Vf0q -yRDfoGGlanGh8sg7BFFnTfDH0dYw8ApYbt/rFT1suFmEaiH44Nd8dldSJxqjK2Ph -B0+4etNpZZEjuUGq0U+oYUeppI4oE3vqAPrP9f/Wd0Kc+Ci56RC8X2lf9TSN4qb9 -xv2a85pUgxBhioZT2fXTDjiUbYKXZhj5tMPixP0M+NcI2yutHpm0Y44BO76bmcVf -8AAfH8RcyJlPwfEGwENzY+CZTqCYbCbSg12CLs9UL6ITx8Z1lt/LnLOGCq672rWb -Pw3wO3Wmam8Zdyyr2Pff+/+ZcL587/Xif+ho48JSWrJynGwOe9WEBjWqYkfcxCP7 -plZxWj8MF7A389eBmpLF95BVLFHJECJYUfN7CPU2g+4cKSuzXenlSWtqDs/GdbeJ -tFMoA9SzFut9tZg7BMA/gXF/2dTMsjU/1Cy1AVzvWl654wB6OYZv8pktwrjQ77qj -S0X4hovoKfpoxYIzw1OTdOuyu7oc6Wh3XWtJWHssrdZuGczDTXtu1qRstFeag/iO -cpvmIaTdvQ== +b250YWN0QGtleWNsb2FrLm9yZxcNMjQxMjIzMTMzNDA4WhgPMjA3NDEyMTExMzM0 +MDhaoDAwLjAfBgNVHSMEGDAWgBRHEnyJC0dXGVQK9QMEzZ+GopZ2lDALBgNVHRQE +BAICEAIwDQYJKoZIhvcNAQELBQADggIBALX2BSzYnZOEHkawBEMJkpa2mz5CTNKK +XLGauiEnWwYkd/lDYG/UqX1q5K2qXqmqdlM3VmwcVYUmIxxUz9eL4vj/PmCvWbmQ +PNwuGoNR44EAUM+0M5hrdbO1BzigTtTD1I5r8BOhMsRDeq/cWDIEqulGqAgjaN2P +dDBqZzIN5ljQeEJ6j3tgSNKbeB+XY9itS5RdYxGY9aPIO7N+zlgvAV+jOgImGApo +uUteq8xgOZVsqquMzuzDwN+OUWsYebxacXmuXFmbLrPLM6qgCUyv4jH73KuyDVib +loubYrx84XexAogZP/IGHSt+NEJv8LeLyGPzhBYBdXCKY5e0qdZMpf7IbrXqm335 +dPqNdLEMWjCaoh2mMmEBZJgifjiW2vekzzHUbWrKcrEv+xvDnaciRYyzPya2xoJz ++GpUkETqLxbgUAlDMAkFpVYBpWfcgi3Tnuxbm183pxmnQw+IoAjHIrLCkQCKQbJX +eqms3bJgVSLxQcflJr3EcdraElrTzaYq9DdbXpcnjrEHmF+Fp74VSe8+fDz6bTTe ++OVXq22vsMsdOmzPg2EGc9u9e6NRWdAarN8sIUee3Nl04K8vuSQVn7ioJJam7llQ +F5JM6nRNtJx5owbq8PiDZYvAQZneEXrXFOWtdMTZ3KKlUPP3yTwlS1Gpa1l+eQT+ +eItihr7GRPDI -----END X509 CRL----- diff --git a/testsuite/integration-arquillian/servers/auth-server/common/keystore/intermediate-ca.crl b/testsuite/integration-arquillian/servers/auth-server/common/keystore/intermediate-ca.crl index 5bf65737f49..ed0f3d974a9 100644 --- a/testsuite/integration-arquillian/servers/auth-server/common/keystore/intermediate-ca.crl +++ b/testsuite/integration-arquillian/servers/auth-server/common/keystore/intermediate-ca.crl @@ -1,19 +1,19 @@ -----BEGIN X509 CRL----- -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 -----END X509 CRL----- diff --git a/testsuite/integration-arquillian/servers/auth-server/common/pki/root/ca/intermediate/crlnumber b/testsuite/integration-arquillian/servers/auth-server/common/pki/root/ca/intermediate/crlnumber index 7d802a3e710..baccd0398f9 100644 --- a/testsuite/integration-arquillian/servers/auth-server/common/pki/root/ca/intermediate/crlnumber +++ b/testsuite/integration-arquillian/servers/auth-server/common/pki/root/ca/intermediate/crlnumber @@ -1 +1 @@ -1002 +1003 diff --git a/testsuite/integration-arquillian/servers/auth-server/common/pki/root/ca/intermediate/crlnumber.old b/testsuite/integration-arquillian/servers/auth-server/common/pki/root/ca/intermediate/crlnumber.old index dd11724042e..7d802a3e710 100644 --- a/testsuite/integration-arquillian/servers/auth-server/common/pki/root/ca/intermediate/crlnumber.old +++ b/testsuite/integration-arquillian/servers/auth-server/common/pki/root/ca/intermediate/crlnumber.old @@ -1 +1 @@ -1001 +1002 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 7de027ab394..10567a6db41 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 @@ -105,6 +105,7 @@ import static org.keycloak.utils.StringUtil.isBlank; public abstract class AbstractX509AuthenticationTest extends AbstractTestRealmKeycloakTest { public static final String EMPTY_CRL_PATH = "empty.crl"; + public static final String EMPTY_EXPIRED_CRL_PATH = "empty-expired.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"; 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 0eb9efe0393..b6b3bbb1fb7 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 @@ -64,6 +64,7 @@ public class X509BrowserCRLTest extends AbstractX509AuthenticationTest { X509AuthenticatorConfigModel config = new X509AuthenticatorConfigModel() .setCRLEnabled(true) + .setCrlAbortIfNonUpdated(true) .setCRLRelativePath(EMPTY_CRL_PATH) .setConfirmationPageAllowed(true) .setMappingSourceType(SUBJECTDN_EMAIL) @@ -71,6 +72,25 @@ public class X509BrowserCRLTest extends AbstractX509AuthenticationTest { x509BrowserLogin(config, userId, "test-user@localhost", "test-user@localhost"); } + @Test + public void loginFailureWithEmptyRevocationListFromFileButExpired() { + // Not possible to test file CRL on undertow at this moment - jboss config dir doesn't exist + ContainerAssume.assumeNotAuthServerUndertow(); + + X509AuthenticatorConfigModel config = + new X509AuthenticatorConfigModel() + .setCRLEnabled(true) + .setCrlAbortIfNonUpdated(true) + .setCRLRelativePath(EMPTY_EXPIRED_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); + + assertLoginFailedDueRevokedCertificate(); + } @Test public void loginFailedWithIntermediateRevocationListFromFile() { @@ -80,6 +100,7 @@ public class X509BrowserCRLTest extends AbstractX509AuthenticationTest { X509AuthenticatorConfigModel config = new X509AuthenticatorConfigModel() .setCRLEnabled(true) + .setCrlAbortIfNonUpdated(true) .setCRLRelativePath(INTERMEDIATE_CA_CRL_PATH) .setConfirmationPageAllowed(true) .setMappingSourceType(SUBJECTDN_EMAIL) @@ -97,6 +118,7 @@ public class X509BrowserCRLTest extends AbstractX509AuthenticationTest { X509AuthenticatorConfigModel config = new X509AuthenticatorConfigModel() .setCRLEnabled(true) + .setCrlAbortIfNonUpdated(true) .setCRLRelativePath(CRLRule.CRL_RESPONDER_ORIGIN + "/" + EMPTY_CRL_PATH) .setConfirmationPageAllowed(true) .setMappingSourceType(SUBJECTDN_EMAIL) @@ -104,12 +126,29 @@ public class X509BrowserCRLTest extends AbstractX509AuthenticationTest { x509BrowserLogin(config, userId, "test-user@localhost", "test-user@localhost"); } + @Test + public void loginFailureWithEmptyRevocationListFromHttpButExpired() { + X509AuthenticatorConfigModel config = + new X509AuthenticatorConfigModel() + .setCRLEnabled(true) + .setCrlAbortIfNonUpdated(true) + .setCRLRelativePath(CRLRule.CRL_RESPONDER_ORIGIN + "/" + EMPTY_EXPIRED_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); + + assertLoginFailedDueRevokedCertificate(); + } @Test public void loginFailedWithIntermediateRevocationListFromHttp() { X509AuthenticatorConfigModel config = new X509AuthenticatorConfigModel() .setCRLEnabled(true) + .setCrlAbortIfNonUpdated(true) .setCRLRelativePath(CRLRule.CRL_RESPONDER_ORIGIN + "/" + INTERMEDIATE_CA_CRL_PATH) .setConfirmationPageAllowed(true) .setMappingSourceType(SUBJECTDN_EMAIL) @@ -127,6 +166,7 @@ public class X509BrowserCRLTest extends AbstractX509AuthenticationTest { X509AuthenticatorConfigModel config = new X509AuthenticatorConfigModel() .setCRLEnabled(true) + .setCrlAbortIfNonUpdated(true) .setCRLRelativePath(CRLRule.CRL_RESPONDER_ORIGIN + "/" + INTERMEDIATE_CA_INVALID_SIGNATURE_CRL_PATH) .setConfirmationPageAllowed(true) .setMappingSourceType(SUBJECTDN_EMAIL) @@ -145,6 +185,7 @@ public class X509BrowserCRLTest extends AbstractX509AuthenticationTest { X509AuthenticatorConfigModel config = new X509AuthenticatorConfigModel() .setCRLEnabled(true) + .setCrlAbortIfNonUpdated(false) .setCRLRelativePath(CRLRule.CRL_RESPONDER_ORIGIN + "/" + INTERMEDIATE_CA_3_CRL_PATH) .setConfirmationPageAllowed(true) .setMappingSourceType(SUBJECTDN_EMAIL) @@ -163,6 +204,7 @@ public class X509BrowserCRLTest extends AbstractX509AuthenticationTest { X509AuthenticatorConfigModel config = new X509AuthenticatorConfigModel() .setCRLEnabled(true) + .setCrlAbortIfNonUpdated(true) .setCRLRelativePath(CRLRule.CRL_RESPONDER_ORIGIN + "/" + EMPTY_CRL_PATH + Constants.CFG_DELIMITER + CRLRule.CRL_RESPONDER_ORIGIN + "/" + INTERMEDIATE_CA_CRL_PATH) .setConfirmationPageAllowed(true) .setMappingSourceType(SUBJECTDN_EMAIL) @@ -182,6 +224,7 @@ public class X509BrowserCRLTest extends AbstractX509AuthenticationTest { X509AuthenticatorConfigModel config = new X509AuthenticatorConfigModel() .setCRLEnabled(true) + .setCrlAbortIfNonUpdated(false) .setCRLRelativePath(CRLRule.CRL_RESPONDER_ORIGIN + "/" + INVALID_CRL_PATH) .setConfirmationPageAllowed(true) .setMappingSourceType(SUBJECTDN_EMAIL) @@ -198,6 +241,7 @@ public class X509BrowserCRLTest extends AbstractX509AuthenticationTest { X509AuthenticatorConfigModel config = new X509AuthenticatorConfigModel() .setCRLEnabled(true) + .setCrlAbortIfNonUpdated(true) .setCRLDistributionPointEnabled(true) .setConfirmationPageAllowed(true) .setMappingSourceType(SUBJECTDN_EMAIL)