Check next update time for CRL in certificate validation

Closes #35983

Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
rmartinc 2024-12-25 10:30:47 +01:00 committed by Marek Posolda
parent d73794cdc1
commit f89be1813d
15 changed files with 190 additions and 73 deletions

View File

@ -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]

View File

@ -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.

View File

@ -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.

View File

@ -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())

View File

@ -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,

View File

@ -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<CRLLoaderImpl> 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<X509CRL> getX509CRLs() throws GeneralSecurityException {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Collection<X509CRL> 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<X509CRL> 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<X509CRL> loadCRLFromLDAP(CertificateFactory cf, URI remoteURI) throws GeneralSecurityException {
private X509CRL loadCRLFromLDAP(CertificateFactory cf, URI remoteURI) throws GeneralSecurityException {
Hashtable<String, String> 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<X509CRL> 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<String> _certificatePolicy;
String _certificatePolicyMode;
boolean _crlCheckingEnabled;
boolean _crlAbortIfNonUpdated;
boolean _crldpEnabled;
CRLLoaderImpl _crlLoader;
boolean _ocspEnabled;
@ -414,6 +428,7 @@ public class CertificateValidator {
int keyUsageBits, List<String> extendedKeyUsage,
List<String> 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<String> 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<X509CRL> 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<String> 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<String> _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);
}
}

View File

@ -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);
}

View File

@ -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());

View File

@ -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-----

View File

@ -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-----

View File

@ -1,19 +1,19 @@
-----BEGIN X509 CRL-----
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
-----END X509 CRL-----

View File

@ -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";

View File

@ -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)