Allow certificate with duplicate principals in truststore.
The previous implementation uses principal as a key for a hashmap storing one certificate per entry. To preserve lookups, the value is now a List of certificates. Additional logic was added to build certification validation chains using signature verification rather than just principal. Closes #33125 Signed-off-by: Matt Eaton <git@divinehawk.com>
This commit is contained in:
parent
07cf71e818
commit
9f0a348e4c
6 changed files with 86 additions and 40 deletions
|
@ -22,6 +22,7 @@ import org.keycloak.provider.Provider;
|
|||
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.KeyStore;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
@ -40,10 +41,10 @@ public interface TruststoreProvider extends Provider {
|
|||
/**
|
||||
* @return root certificates from the configured truststore as a map where the key is the X500Principal of the corresponding X509Certificate
|
||||
*/
|
||||
Map<X500Principal, X509Certificate> getRootCertificates();
|
||||
Map<X500Principal, List<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();
|
||||
Map<X500Principal, List<X509Certificate>> getIntermediateCertificates();
|
||||
}
|
||||
|
|
|
@ -29,6 +29,8 @@ import java.io.InputStream;
|
|||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.CRLException;
|
||||
import java.security.cert.CertPathBuilder;
|
||||
import java.security.cert.CertPathValidatorException;
|
||||
|
@ -589,8 +591,8 @@ public class CertificateValidator {
|
|||
}
|
||||
else
|
||||
{
|
||||
Set<X509Certificate> trustedRootCerts = truststoreProvider.getRootCertificates().entrySet().stream().map(t -> t.getValue()).collect(Collectors.toSet());
|
||||
Set<X509Certificate> trustedIntermediateCerts = truststoreProvider.getIntermediateCertificates().entrySet().stream().map(t -> t.getValue()).collect(Collectors.toSet());
|
||||
Set<X509Certificate> trustedRootCerts = truststoreProvider.getRootCertificates().entrySet().stream().flatMap(t -> t.getValue().stream()).collect(Collectors.toSet());
|
||||
Set<X509Certificate> trustedIntermediateCerts = truststoreProvider.getIntermediateCertificates().entrySet().stream().flatMap(t -> t.getValue().stream()).collect(Collectors.toSet());
|
||||
|
||||
logger.debugf("Found %d trusted root certs, %d trusted intermediate certs", trustedRootCerts.size(), trustedIntermediateCerts.size());
|
||||
|
||||
|
@ -651,16 +653,30 @@ public class CertificateValidator {
|
|||
return result;
|
||||
}
|
||||
|
||||
private X509Certificate findCAInTruststore(X500Principal issuer) throws GeneralSecurityException {
|
||||
private X509Certificate findCAInTruststore(X509Certificate cert) throws GeneralSecurityException {
|
||||
TruststoreProvider truststoreProvider = session.getProvider(TruststoreProvider.class);
|
||||
if (truststoreProvider == null || truststoreProvider.getTruststore() == null) {
|
||||
return null;
|
||||
}
|
||||
Map<X500Principal, X509Certificate> rootCerts = truststoreProvider.getRootCertificates();
|
||||
X509Certificate ca = rootCerts.get(issuer);
|
||||
if (ca == null) {
|
||||
Map<X500Principal, List<X509Certificate>> rootCerts = truststoreProvider.getRootCertificates();
|
||||
X500Principal issuer = cert.getIssuerX500Principal();
|
||||
List<X509Certificate> cas = rootCerts.get(issuer);
|
||||
X509Certificate ca = null;
|
||||
if (cas == null) {
|
||||
// fallback to lookup the issuer from the list of intermediary CAs
|
||||
ca = truststoreProvider.getIntermediateCertificates().get(issuer);
|
||||
cas = truststoreProvider.getIntermediateCertificates().get(issuer);
|
||||
}
|
||||
// find the one that signed the cert
|
||||
if (cas != null) {
|
||||
for (X509Certificate cacert : cas) {
|
||||
try {
|
||||
cert.verify(cacert.getPublicKey());
|
||||
} catch (InvalidKeyException | SignatureException e) {
|
||||
continue;
|
||||
}
|
||||
ca = cacert;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ca != null) {
|
||||
ca.checkValidity();
|
||||
|
@ -687,7 +703,7 @@ public class CertificateValidator {
|
|||
} else {
|
||||
// only one cert => find the CA certificate using the truststore SPI
|
||||
cert = certs[0];
|
||||
issuer = findCAInTruststore(cert.getIssuerX500Principal());
|
||||
issuer = findCAInTruststore(cert);
|
||||
if (issuer == null) {
|
||||
throw new GeneralSecurityException(
|
||||
String.format("No trusted CA in certificate found: %s. Add it to truststore SPI if valid.",
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.keycloak.truststore.TruststoreProviderFactory;
|
|||
import java.security.cert.X509Certificate;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* The factory and the corresponding providers extract a client certificate
|
||||
|
@ -84,8 +85,11 @@ public class NginxProxySslClientCertificateLookupFactory extends AbstractClientC
|
|||
TruststoreProvider provider = truststoreFactory.create(kcSession);
|
||||
|
||||
if (provider != null && provider.getTruststore() != null) {
|
||||
trustedRootCerts.addAll(provider.getRootCertificates().values());
|
||||
intermediateCerts.addAll(provider.getIntermediateCertificates().values());
|
||||
Set<X509Certificate> rootCertificates = provider.getRootCertificates().entrySet().stream().flatMap(t -> t.getValue().stream()).collect(Collectors.toSet());
|
||||
Set<X509Certificate> intermediateCertficiates = provider.getIntermediateCertificates().entrySet().stream().flatMap(t -> t.getValue().stream()).collect(Collectors.toSet());
|
||||
|
||||
trustedRootCerts.addAll(rootCertificates);
|
||||
intermediateCerts.addAll(intermediateCertficiates);
|
||||
logger.debug("Keycloak truststore loaded for NGINX x509cert-lookup provider.");
|
||||
|
||||
isTruststoreLoaded = true;
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.keycloak.common.enums.HostnameVerificationPolicy;
|
|||
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
@ -33,10 +34,10 @@ public class FileTruststoreProvider implements TruststoreProvider {
|
|||
private final HostnameVerificationPolicy policy;
|
||||
private final SSLSocketFactory sslSocketFactory;
|
||||
private final KeyStore truststore;
|
||||
private final Map<X500Principal, X509Certificate> rootCertificates;
|
||||
private final Map<X500Principal, X509Certificate> intermediateCertificates;
|
||||
private final Map<X500Principal, List<X509Certificate>> rootCertificates;
|
||||
private final Map<X500Principal, List<X509Certificate>> intermediateCertificates;
|
||||
|
||||
public FileTruststoreProvider(KeyStore truststore, HostnameVerificationPolicy policy, Map<X500Principal, X509Certificate> rootCertificates, Map<X500Principal, X509Certificate> intermediateCertificates) {
|
||||
public FileTruststoreProvider(KeyStore truststore, HostnameVerificationPolicy policy, Map<X500Principal, List<X509Certificate>> rootCertificates,Map<X500Principal, List<X509Certificate>> intermediateCertificates) {
|
||||
this.policy = policy;
|
||||
this.truststore = truststore;
|
||||
this.rootCertificates = rootCertificates;
|
||||
|
@ -62,12 +63,12 @@ public class FileTruststoreProvider implements TruststoreProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Map<X500Principal, X509Certificate> getRootCertificates() {
|
||||
public Map<X500Principal, List<X509Certificate>> getRootCertificates() {
|
||||
return rootCertificates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<X500Principal, X509Certificate> getIntermediateCertificates() {
|
||||
public Map<X500Principal, List<X509Certificate>> getIntermediateCertificates() {
|
||||
return intermediateCertificates;
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ import java.security.SignatureException;
|
|||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
|
@ -176,8 +177,8 @@ public class FileTruststoreProviderFactory implements TruststoreProviderFactory
|
|||
|
||||
private static class TruststoreCertificatesLoader {
|
||||
|
||||
private Map<X500Principal, X509Certificate> trustedRootCerts = new HashMap<>();
|
||||
private Map<X500Principal, X509Certificate> intermediateCerts = new HashMap<>();
|
||||
private Map<X500Principal, List<X509Certificate>> trustedRootCerts = new HashMap<>();
|
||||
private Map<X500Principal, List<X509Certificate>> intermediateCerts = new HashMap<>();
|
||||
|
||||
|
||||
public TruststoreCertificatesLoader(KeyStore truststore) {
|
||||
|
@ -213,11 +214,21 @@ public class FileTruststoreProviderFactory implements TruststoreProviderFactory
|
|||
X509Certificate cax509cert = (X509Certificate) certificate;
|
||||
if (isSelfSigned(cax509cert)) {
|
||||
X500Principal principal = cax509cert.getSubjectX500Principal();
|
||||
trustedRootCerts.put(principal, cax509cert);
|
||||
List<X509Certificate> certs = trustedRootCerts.get(principal);
|
||||
if (certs == null) {
|
||||
certs = new ArrayList<>();
|
||||
trustedRootCerts.put(principal, certs);
|
||||
}
|
||||
certs.add(cax509cert);
|
||||
log.debug("Trusted root CA found in truststore : alias : " + alias + " | Subject DN : " + principal);
|
||||
} else {
|
||||
X500Principal principal = cax509cert.getSubjectX500Principal();
|
||||
intermediateCerts.put(principal, cax509cert);
|
||||
List<X509Certificate> certs = intermediateCerts.get(principal);
|
||||
if (certs == null) {
|
||||
certs = new ArrayList<>();
|
||||
intermediateCerts.put(principal, certs);
|
||||
}
|
||||
certs.add(cax509cert);
|
||||
log.debug("Intermediate CA found in truststore : alias : " + alias + " | Subject DN : " + principal);
|
||||
}
|
||||
} else
|
||||
|
|
|
@ -18,9 +18,12 @@
|
|||
package org.keycloak.utils;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.X509CRL;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -71,7 +74,7 @@ public final class CRLUtils {
|
|||
// Try to find the CRL issuer certificate in the truststore
|
||||
if (crlSignatureCertificate == null) {
|
||||
log.tracef("Not found CRL issuer '%s' in the CA chain of the certificate. Fallback to lookup CRL issuer in the truststore", crlIssuerPrincipal);
|
||||
crlSignatureCertificate = findCRLSignatureCertificateInTruststore(session, certs, crlIssuerPrincipal);
|
||||
crlSignatureCertificate = findCRLSignatureCertificateInTruststore(session, certs, crl);
|
||||
}
|
||||
|
||||
// Verify signature on CRL
|
||||
|
@ -87,18 +90,31 @@ public final class CRLUtils {
|
|||
}
|
||||
|
||||
|
||||
private static X509Certificate findCRLSignatureCertificateInTruststore(KeycloakSession session, X509Certificate[] certs, X500Principal crlIssuerPrincipal) throws GeneralSecurityException {
|
||||
private static X509Certificate findCRLSignatureCertificateInTruststore(KeycloakSession session, X509Certificate[] certs, X509CRL crl) 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();
|
||||
X500Principal crlIssuerPrincipal = crl.getIssuerX500Principal();
|
||||
Map<X500Principal, List<X509Certificate>> rootCerts = truststoreProvider.getRootCertificates();
|
||||
Map<X500Principal, List<X509Certificate>> intermediateCerts = truststoreProvider.getIntermediateCertificates();
|
||||
|
||||
X509Certificate crlSignatureCertificate = intermediateCerts.get(crlIssuerPrincipal);
|
||||
if (crlSignatureCertificate == null) {
|
||||
crlSignatureCertificate = rootCerts.get(crlIssuerPrincipal);
|
||||
List<X509Certificate> crlSignatureCertificates = intermediateCerts.get(crlIssuerPrincipal);
|
||||
X509Certificate crlSignatureCertificate = null;
|
||||
|
||||
if (crlSignatureCertificates == null) {
|
||||
crlSignatureCertificates = rootCerts.get(crlIssuerPrincipal);
|
||||
}
|
||||
|
||||
for (X509Certificate cacert : crlSignatureCertificates) {
|
||||
try {
|
||||
crl.verify(cacert.getPublicKey());
|
||||
} catch (InvalidKeyException | SignatureException e) {
|
||||
continue;
|
||||
}
|
||||
crlSignatureCertificate = cacert;
|
||||
break;
|
||||
}
|
||||
|
||||
if (crlSignatureCertificate == null) {
|
||||
|
@ -112,25 +128,22 @@ public final class CRLUtils {
|
|||
.map(X509Certificate::getIssuerX500Principal)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
X509Certificate currentCRLAnchorCertificate = crlSignatureCertificate;
|
||||
X500Principal currentCRLAnchorPrincipal = crlIssuerPrincipal;
|
||||
|
||||
for (X500Principal certificateCAPrincipal : certificateCAPrincipals) {
|
||||
if (certificateCAPrincipal.equals(currentCRLAnchorPrincipal)) {
|
||||
log.tracef("Found trust anchor of the CRL issuer '%s' in the CA chain. Anchor is '%s'", crlIssuerPrincipal, currentCRLAnchorPrincipal);
|
||||
break;
|
||||
return crlSignatureCertificate;
|
||||
}
|
||||
}
|
||||
|
||||
// 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.");
|
||||
}
|
||||
// Anchor was not in the provided certificate chain, check the truststore
|
||||
List<X509Certificate> currentCRLAnchorCertificates = intermediateCerts.get(currentCRLAnchorPrincipal);
|
||||
if (currentCRLAnchorCertificates == null) {
|
||||
currentCRLAnchorCertificates = rootCerts.get(currentCRLAnchorPrincipal);
|
||||
}
|
||||
if (currentCRLAnchorCertificates == 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;
|
||||
|
|
Loading…
Reference in a new issue