KEYCLOAK-9846 Verifying signatures on CRL during X509 authentication

This commit is contained in:
mposolda 2019-03-21 11:55:49 +01:00 committed by Marek Posolda
parent 2899375614
commit 5f9feee3f8
22 changed files with 562 additions and 217 deletions

View file

@ -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 <a href="mailto:brat000012001@gmail.com">Peter Nalyvayko</a>
* @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 <a href="www.nakov.com/blog/2009/12/01/x509-certificate-validation-in-java-build-and-verify-cchain-and-verify-clr-with-bouncy-castle/">CRL validation</a>
* @param cert
* @return
* @throws IOException
*/
public static List<String> getCRLDistributionPoints(X509Certificate cert) throws IOException {
byte[] data = cert.getExtensionValue(CRL_DISTRIBUTION_POINTS_OID);
if (data == null) {
return Collections.emptyList();
}
List<String> 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;
}
}

View file

@ -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 <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -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<X500Principal, X509Certificate> getRootCertificates();
/**
* @return intermediate certificates from the configured truststore as a map where the key is the X500Principal of the corresponding X509Certificate
*/
Map<X500Principal, X509Certificate> getIntermediateCertificates();
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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<X509Certificate>();
intermediateCerts = new HashSet<X509Certificate>();
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;
}
}

View file

@ -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 <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -26,10 +30,14 @@ public class FileTruststoreProvider implements TruststoreProvider {
private final HostnameVerificationPolicy policy;
private final KeyStore truststore;
private final Map<X500Principal, X509Certificate> rootCertificates;
private final Map<X500Principal, X509Certificate> intermediateCertificates;
FileTruststoreProvider(KeyStore truststore, HostnameVerificationPolicy policy) {
FileTruststoreProvider(KeyStore truststore, HostnameVerificationPolicy policy, Map<X500Principal, X509Certificate> rootCertificates, Map<X500Principal, X509Certificate> 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<X500Principal, X509Certificate> getRootCertificates() {
return rootCertificates;
}
@Override
public Map<X500Principal, X509Certificate> getIntermediateCertificates() {
return intermediateCertificates;
}
@Override
public void close() {
}

View file

@ -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 <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -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<X500Principal, X509Certificate> trustedRootCerts = new HashMap<>();
private Map<X500Principal, X509Certificate> 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;
}
}
}

View file

@ -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 <a href="mailto:brat000012001@gmail.com">Peter Nalyvayko</a>
* @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 <a href="www.nakov.com/blog/2009/12/01/x509-certificate-validation-in-java-build-and-verify-cchain-and-verify-clr-with-bouncy-castle/">CRL validation</a>
* @param cert
* @return
* @throws IOException
*/
public static List<String> getCRLDistributionPoints(X509Certificate cert) throws IOException {
byte[] data = cert.getExtensionValue(CRL_DISTRIBUTION_POINTS_OID);
if (data == null) {
return Collections.emptyList();
}
List<String> 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<certs.length ; i++) {
X509Certificate currentCACert = certs[i];
if (crlIssuerPrincipal.equals(currentCACert.getSubjectX500Principal())) {
crlSignatureCertificate = currentCACert;
log.tracef("Found certificate used to sign CRL in the CA chain of the certificate. CRL issuer: %s", crlIssuerPrincipal);
break;
}
}
// 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, crlIssuerPrincipal);
}
// 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());
log.debug(message);
throw new GeneralSecurityException(message);
}
}
private static X509Certificate findCRLSignatureCertificateInTruststore(KeycloakSession session, X509Certificate[] certs, X500Principal crlIssuerPrincipal) throws GeneralSecurityException {
TruststoreProvider truststoreProvider = session.getProvider(TruststoreProvider.class);
if (truststoreProvider == null || truststoreProvider.getTruststore() == null) {
throw new GeneralSecurityException("Truststore not available");
}
Map<X500Principal, X509Certificate> rootCerts = truststoreProvider.getRootCertificates();
Map<X500Principal, X509Certificate> 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<X500Principal> 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;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -190,8 +190,7 @@
<include>ca.crt</include>
<include>client.crt</include>
<include>client.key</include>
<include>intermediate-ca.crl</include>
<include>empty.crl</include>
<include>*.crl</include>
<!-- KEYCLOAK-6771 Certificate Bound Token -->
<include>other_client.jks</include>
</includes>

View file

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

View file

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

View file

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