KEYCLOAK-9846 Verifying signatures on CRL during X509 authentication
This commit is contained in:
parent
2899375614
commit
5f9feee3f8
22 changed files with 562 additions and 217 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
206
services/src/main/java/org/keycloak/utils/CRLUtils.java
Normal file
206
services/src/main/java/org/keycloak/utils/CRLUtils.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
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
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIF/jCCA+agAwIBAgIJAOMEN39fZf7uMA0GCSqGSIb3DQEBCwUAMIGLMQswCQYD
|
||||
VQQGEwJVUzELMAkGA1UECAwCTUExDzANBgNVBAcMBkJvc3RvbjEQMA4GA1UECgwH
|
||||
UmVkIEhhdDERMA8GA1UECwwIS2V5Y2xvYWsxFDASBgNVBAMMC0tleWNsb2FrIENB
|
||||
MSMwIQYJKoZIhvcNAQkBFhRjb250YWN0QGtleWNsb2FrLm9yZzAeFw0xODAyMjAx
|
||||
OTQ3NTFaFw00NTA3MDgxOTQ3NTFaMIGLMQswCQYDVQQGEwJVUzELMAkGA1UECAwC
|
||||
TUExDzANBgNVBAcMBkJvc3RvbjEQMA4GA1UECgwHUmVkIEhhdDERMA8GA1UECwwI
|
||||
S2V5Y2xvYWsxFDASBgNVBAMMC0tleWNsb2FrIENBMSMwIQYJKoZIhvcNAQkBFhRj
|
||||
b250YWN0QGtleWNsb2FrLm9yZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
|
||||
ggIBAJlGjg05FzCm3f3YdIbMHNYuORfiP2n6YhX7vQyDjF/4gh7EYEYgE7spJ864
|
||||
/DySQenJ55Jn22K/1MQ1rOHcqfTioIgN3eEAyyuMDx60KU3frMBRYeCgLJVZQHr0
|
||||
6x+Sh/+SbbIYq/558+g/6PSZjmPBindHsPzGuBPaLOW4Jz47CA73L/su2qnJGeAi
|
||||
UaK/tXmANs1bqJbiNRDr9IJFkdusx1mql2ElfknJT8U+LBPOOID/S7Xd83SKtpFI
|
||||
Q8Vikb6C0SKnopOJiG2uWg5g7CYlNYxJpAM25zhDqp71bl8zOsIL2tFfUAvvoBnh
|
||||
N31kDIl8RZJ5ELnh+t5SCfwbgdfMzS7uht8qVTeZ0/BG80Lzl1gfzNR8q45gsKC9
|
||||
7mg7Voj68kt2aZr+E3Ng1guK69gePMxCpqLyjwlKz187mNUme+zxg2gL2egs4M6u
|
||||
ffqsEd0c5QryrRSTcIXi8Bim6PDhL93dBsenAIg25DOJNA6Vt2LELoe9w0TkL48U
|
||||
wUvU6GYB7/zM/z3EW45ZkRhHWK+HZppqDAb05lgJeeKUxxdUSy+ot7ls6cSqACYo
|
||||
fVjPoVHPD5Ncx+6NGHPGM5N3FGvMMh64PYpChyVWDTEfrZIS7Yyj9Iz/2eCxV3cO
|
||||
cO4bU0K6kx/dWRic5B5ymVtRME93+Of/hQuta4uLhlo8ZxRpAgMBAAGjYzBhMB0G
|
||||
A1UdDgQWBBQiuPS7cwDHKT+TgKX2HFICast6UjAfBgNVHSMEGDAWgBQiuPS7cwDH
|
||||
KT+TgKX2HFICast6UjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAN
|
||||
BgkqhkiG9w0BAQsFAAOCAgEAVCVXdx79ooKyOaL+S49S4agP7mE4IxuDefDwQ2dm
|
||||
996wpk3nntg0y54Auu1Y2plJirBhTvYZ1RedLNBMVBypm6BQpNn37u5TI39/FYso
|
||||
GFPINu1EzLTYl0bFKc0w7UFlFusje9zXLWISm8uTNzxJ1RGLrcnv9gfiXPKxAmN0
|
||||
cz9WY0vm+0+OV50HvLyUyqGKxyWmt2ek4jV+oEhsMMSO/MVNNXHEo2MAGcA23XPe
|
||||
7FZkiFB1suDIMzzUFCrRBtoZjYSUeyN9Pd0Yg3twl96CLqld4xFjsKMIsz0ACGRI
|
||||
8OpzeHAsePH4yS94E6nLwWH9YTi6pgTtoXSaVBLvIYpVHi8UAyIBFNqLMCukoq0O
|
||||
BlOdkO0zescmpEtp8GiUWMuB7x+kkaSxmsujEfL3mRWshkqaz/ZHPKXaNtPBUtIM
|
||||
jQnTMBF/wQjZxCGAps8dOMZ9pYnZcmVz0KeXpBJe1j+47MhItgt1wQNoyr4iBaxE
|
||||
3fAF/Arr/IZtIf0erXOjc7P6dEQW+WiKWvEA5Mp+4tV3Zj2pwSSX5bKDKx4RAkoW
|
||||
1jLTE1KN5RWvF8phStLty83gTd5wgykFSl65Lu7KIBW9HH3LIK46fb+cOBOZfSn3
|
||||
mdQXrbuXNUXgbhrsetnBfPNMAkJjaBQLNTxebIvXndiTIEsWqHS7h1x+kBkDOKhw
|
||||
SCc=
|
||||
-----END CERTIFICATE-----
|
||||
|
|
|
@ -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-----
|
|
@ -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-----
|
Binary file not shown.
|
@ -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-----
|
|
@ -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-----
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Binary file not shown.
Loading…
Reference in a new issue